Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: PDS 4 Driver; Planetary Data System Format
4 : * Purpose: Implementation of PDS4Dataset
5 : * Author: Even Rouault, even.rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2017, Hobu Inc
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_vsi_error.h"
14 : #include "gdal_proxy.h"
15 : #include "gdal_frmts.h"
16 : #include "rawdataset.h"
17 : #include "vrtdataset.h"
18 : #include "ogrsf_frmts.h"
19 : #include "ogr_spatialref.h"
20 : #include "gdal_priv_templates.hpp"
21 : #include "ogreditablelayer.h"
22 : #include "pds4dataset.h"
23 : #include "pdsdrivercore.h"
24 :
25 : #ifdef EMBED_RESOURCE_FILES
26 : #include "embedded_resources.h"
27 : #endif
28 :
29 : #include <cstdlib>
30 : #include <vector>
31 : #include <algorithm>
32 : #include <optional>
33 :
34 : #define TIFF_GEOTIFF_STRING "TIFF 6.0"
35 : #define BIGTIFF_GEOTIFF_STRING "TIFF 6.0"
36 : #define PREEXISTING_BINARY_FILE \
37 : "Binary file pre-existing PDS4 label. This comment is used by GDAL to " \
38 : "avoid deleting the binary file when the label is deleted. Keep it to " \
39 : "preserve this behavior."
40 :
41 : #define CURRENT_CART_VERSION "1O00_1970"
42 :
43 : /************************************************************************/
44 : /* PDS4BrowseImageProxyRasterBand() */
45 : /************************************************************************/
46 :
47 2 : PDS4BrowseImageProxyRasterBand::PDS4BrowseImageProxyRasterBand(
48 2 : GDALRasterBand *poBaseBandIn)
49 2 : : m_poBaseBand(poBaseBandIn)
50 : {
51 2 : eDataType = m_poBaseBand->GetRasterDataType();
52 2 : m_poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
53 2 : }
54 :
55 : /************************************************************************/
56 : /* RefUnderlyingRasterBand() */
57 : /************************************************************************/
58 :
59 3 : GDALRasterBand *PDS4BrowseImageProxyRasterBand::RefUnderlyingRasterBand(
60 : bool /*bForceOpen*/) const
61 : {
62 3 : return m_poBaseBand;
63 : }
64 :
65 : /************************************************************************/
66 : /* PDS4WrapperRasterBand() */
67 : /************************************************************************/
68 :
69 24 : PDS4WrapperRasterBand::PDS4WrapperRasterBand(GDALRasterBand *poBaseBandIn)
70 24 : : m_poBaseBand(poBaseBandIn)
71 : {
72 24 : eDataType = m_poBaseBand->GetRasterDataType();
73 24 : m_poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
74 24 : }
75 :
76 : /************************************************************************/
77 : /* SetMaskBand() */
78 : /************************************************************************/
79 :
80 0 : void PDS4WrapperRasterBand::SetMaskBand(
81 : std::unique_ptr<GDALRasterBand> poMaskBand)
82 : {
83 0 : poMask.reset(std::move(poMaskBand));
84 0 : nMaskFlags = 0;
85 0 : }
86 :
87 : /************************************************************************/
88 : /* GetOffset() */
89 : /************************************************************************/
90 :
91 18 : double PDS4WrapperRasterBand::GetOffset(int *pbSuccess)
92 : {
93 18 : if (pbSuccess)
94 18 : *pbSuccess = m_bHasOffset;
95 18 : return m_dfOffset;
96 : }
97 :
98 : /************************************************************************/
99 : /* GetScale() */
100 : /************************************************************************/
101 :
102 18 : double PDS4WrapperRasterBand::GetScale(int *pbSuccess)
103 : {
104 18 : if (pbSuccess)
105 18 : *pbSuccess = m_bHasScale;
106 18 : return m_dfScale;
107 : }
108 :
109 : /************************************************************************/
110 : /* SetOffset() */
111 : /************************************************************************/
112 :
113 1 : CPLErr PDS4WrapperRasterBand::SetOffset(double dfNewOffset)
114 : {
115 1 : m_dfOffset = dfNewOffset;
116 1 : m_bHasOffset = true;
117 :
118 1 : PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
119 1 : if (poGDS->m_poExternalDS && eAccess == GA_Update)
120 1 : poGDS->m_poExternalDS->GetRasterBand(nBand)->SetOffset(dfNewOffset);
121 :
122 1 : return CE_None;
123 : }
124 :
125 : /************************************************************************/
126 : /* SetScale() */
127 : /************************************************************************/
128 :
129 1 : CPLErr PDS4WrapperRasterBand::SetScale(double dfNewScale)
130 : {
131 1 : m_dfScale = dfNewScale;
132 1 : m_bHasScale = true;
133 :
134 1 : PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
135 1 : if (poGDS->m_poExternalDS && eAccess == GA_Update)
136 1 : poGDS->m_poExternalDS->GetRasterBand(nBand)->SetScale(dfNewScale);
137 :
138 1 : return CE_None;
139 : }
140 :
141 : /************************************************************************/
142 : /* GetNoDataValue() */
143 : /************************************************************************/
144 :
145 35 : double PDS4WrapperRasterBand::GetNoDataValue(int *pbSuccess)
146 : {
147 35 : if (m_bHasNoDataInt64)
148 : {
149 0 : if (pbSuccess)
150 0 : *pbSuccess = true;
151 0 : return GDALGetNoDataValueCastToDouble(m_nNoDataInt64);
152 : }
153 :
154 35 : if (m_bHasNoDataUInt64)
155 : {
156 0 : if (pbSuccess)
157 0 : *pbSuccess = true;
158 0 : return GDALGetNoDataValueCastToDouble(m_nNoDataUInt64);
159 : }
160 :
161 35 : if (pbSuccess)
162 35 : *pbSuccess = m_bHasNoData;
163 35 : return m_dfNoData;
164 : }
165 :
166 : /************************************************************************/
167 : /* SetNoDataValue() */
168 : /************************************************************************/
169 :
170 9 : CPLErr PDS4WrapperRasterBand::SetNoDataValue(double dfNewNoData)
171 : {
172 9 : m_dfNoData = dfNewNoData;
173 9 : m_bHasNoData = true;
174 :
175 9 : PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
176 9 : if (poGDS->m_poExternalDS && eAccess == GA_Update)
177 9 : poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValue(
178 9 : dfNewNoData);
179 :
180 9 : return CE_None;
181 : }
182 :
183 : /************************************************************************/
184 : /* GetNoDataValueAsInt64() */
185 : /************************************************************************/
186 :
187 3 : int64_t PDS4WrapperRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
188 : {
189 3 : if (pbSuccess)
190 3 : *pbSuccess = m_bHasNoDataInt64;
191 3 : return m_nNoDataInt64;
192 : }
193 :
194 : /************************************************************************/
195 : /* SetNoDataValueAsInt64() */
196 : /************************************************************************/
197 :
198 1 : CPLErr PDS4WrapperRasterBand::SetNoDataValueAsInt64(int64_t nNewNoData)
199 : {
200 1 : m_nNoDataInt64 = nNewNoData;
201 1 : m_bHasNoDataInt64 = true;
202 :
203 1 : PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
204 1 : if (poGDS->m_poExternalDS && eAccess == GA_Update)
205 1 : poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValueAsInt64(
206 1 : nNewNoData);
207 :
208 1 : return CE_None;
209 : }
210 :
211 : /************************************************************************/
212 : /* GetNoDataValueAsUInt64() */
213 : /************************************************************************/
214 :
215 3 : uint64_t PDS4WrapperRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
216 : {
217 3 : if (pbSuccess)
218 3 : *pbSuccess = m_bHasNoDataUInt64;
219 3 : return m_nNoDataUInt64;
220 : }
221 :
222 : /************************************************************************/
223 : /* SetNoDataValueAsUInt64() */
224 : /************************************************************************/
225 :
226 1 : CPLErr PDS4WrapperRasterBand::SetNoDataValueAsUInt64(uint64_t nNewNoData)
227 : {
228 1 : m_nNoDataUInt64 = nNewNoData;
229 1 : m_bHasNoDataUInt64 = true;
230 :
231 1 : PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
232 1 : if (poGDS->m_poExternalDS && eAccess == GA_Update)
233 1 : poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValueAsUInt64(
234 1 : nNewNoData);
235 :
236 1 : return CE_None;
237 : }
238 :
239 : /************************************************************************/
240 : /* Fill() */
241 : /************************************************************************/
242 :
243 2 : CPLErr PDS4WrapperRasterBand::Fill(double dfRealValue, double dfImaginaryValue)
244 : {
245 2 : PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
246 2 : if (poGDS->m_bMustInitImageFile)
247 : {
248 2 : if (!poGDS->InitImageFile())
249 0 : return CE_Failure;
250 : }
251 2 : return GDALProxyRasterBand::Fill(dfRealValue, dfImaginaryValue);
252 : }
253 :
254 : /************************************************************************/
255 : /* IWriteBlock() */
256 : /************************************************************************/
257 :
258 0 : CPLErr PDS4WrapperRasterBand::IWriteBlock(int nXBlock, int nYBlock,
259 : void *pImage)
260 :
261 : {
262 0 : PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
263 0 : if (poGDS->m_bMustInitImageFile)
264 : {
265 0 : if (!poGDS->InitImageFile())
266 0 : return CE_Failure;
267 : }
268 0 : return GDALProxyRasterBand::IWriteBlock(nXBlock, nYBlock, pImage);
269 : }
270 :
271 : /************************************************************************/
272 : /* IRasterIO() */
273 : /************************************************************************/
274 :
275 115 : CPLErr PDS4WrapperRasterBand::IRasterIO(
276 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
277 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
278 : GSpacing nPixelSpace, GSpacing nLineSpace, GDALRasterIOExtraArg *psExtraArg)
279 :
280 : {
281 115 : PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
282 115 : if (eRWFlag == GF_Write && poGDS->m_bMustInitImageFile)
283 : {
284 9 : if (!poGDS->InitImageFile())
285 0 : return CE_Failure;
286 : }
287 115 : return GDALProxyRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
288 : pData, nBufXSize, nBufYSize, eBufType,
289 115 : nPixelSpace, nLineSpace, psExtraArg);
290 : }
291 :
292 : /************************************************************************/
293 : /* PDS4RawRasterBand() */
294 : /************************************************************************/
295 :
296 460 : PDS4RawRasterBand::PDS4RawRasterBand(GDALDataset *l_poDS, int l_nBand,
297 : VSILFILE *l_fpRaw,
298 : vsi_l_offset l_nImgOffset,
299 : int l_nPixelOffset, int l_nLineOffset,
300 : GDALDataType l_eDataType,
301 460 : RawRasterBand::ByteOrder eByteOrderIn)
302 : : RawRasterBand(l_poDS, l_nBand, l_fpRaw, l_nImgOffset, l_nPixelOffset,
303 : l_nLineOffset, l_eDataType, eByteOrderIn,
304 460 : RawRasterBand::OwnFP::NO)
305 : {
306 460 : }
307 :
308 : /************************************************************************/
309 : /* SetMaskBand() */
310 : /************************************************************************/
311 :
312 43 : void PDS4RawRasterBand::SetMaskBand(std::unique_ptr<GDALRasterBand> poMaskBand)
313 : {
314 43 : poMask.reset(std::move(poMaskBand));
315 43 : nMaskFlags = 0;
316 43 : }
317 :
318 : /************************************************************************/
319 : /* GetOffset() */
320 : /************************************************************************/
321 :
322 127 : double PDS4RawRasterBand::GetOffset(int *pbSuccess)
323 : {
324 127 : if (pbSuccess)
325 119 : *pbSuccess = m_bHasOffset;
326 127 : return m_dfOffset;
327 : }
328 :
329 : /************************************************************************/
330 : /* GetScale() */
331 : /************************************************************************/
332 :
333 127 : double PDS4RawRasterBand::GetScale(int *pbSuccess)
334 : {
335 127 : if (pbSuccess)
336 119 : *pbSuccess = m_bHasScale;
337 127 : return m_dfScale;
338 : }
339 :
340 : /************************************************************************/
341 : /* SetOffset() */
342 : /************************************************************************/
343 :
344 289 : CPLErr PDS4RawRasterBand::SetOffset(double dfNewOffset)
345 : {
346 289 : m_dfOffset = dfNewOffset;
347 289 : m_bHasOffset = true;
348 289 : return CE_None;
349 : }
350 :
351 : /************************************************************************/
352 : /* SetScale() */
353 : /************************************************************************/
354 :
355 289 : CPLErr PDS4RawRasterBand::SetScale(double dfNewScale)
356 : {
357 289 : m_dfScale = dfNewScale;
358 289 : m_bHasScale = true;
359 289 : return CE_None;
360 : }
361 :
362 : /************************************************************************/
363 : /* GetNoDataValue() */
364 : /************************************************************************/
365 :
366 230 : double PDS4RawRasterBand::GetNoDataValue(int *pbSuccess)
367 : {
368 230 : if (m_bHasNoDataInt64)
369 : {
370 0 : if (pbSuccess)
371 0 : *pbSuccess = true;
372 0 : return GDALGetNoDataValueCastToDouble(m_nNoDataInt64);
373 : }
374 :
375 230 : if (m_bHasNoDataUInt64)
376 : {
377 0 : if (pbSuccess)
378 0 : *pbSuccess = true;
379 0 : return GDALGetNoDataValueCastToDouble(m_nNoDataUInt64);
380 : }
381 :
382 230 : if (pbSuccess)
383 230 : *pbSuccess = m_bHasNoData;
384 230 : return m_dfNoData;
385 : }
386 :
387 : /************************************************************************/
388 : /* SetNoDataValue() */
389 : /************************************************************************/
390 :
391 92 : CPLErr PDS4RawRasterBand::SetNoDataValue(double dfNewNoData)
392 : {
393 92 : m_dfNoData = dfNewNoData;
394 92 : m_bHasNoData = true;
395 92 : return CE_None;
396 : }
397 :
398 : /************************************************************************/
399 : /* GetNoDataValueAsInt64() */
400 : /************************************************************************/
401 :
402 9 : int64_t PDS4RawRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
403 : {
404 9 : if (pbSuccess)
405 9 : *pbSuccess = m_bHasNoDataInt64;
406 9 : return m_nNoDataInt64;
407 : }
408 :
409 : /************************************************************************/
410 : /* SetNoDataValueAsInt64() */
411 : /************************************************************************/
412 :
413 3 : CPLErr PDS4RawRasterBand::SetNoDataValueAsInt64(int64_t nNewNoData)
414 : {
415 3 : m_nNoDataInt64 = nNewNoData;
416 3 : m_bHasNoDataInt64 = true;
417 :
418 3 : PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
419 3 : if (poGDS->m_poExternalDS && eAccess == GA_Update)
420 0 : poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValueAsInt64(
421 0 : nNewNoData);
422 :
423 3 : return CE_None;
424 : }
425 :
426 : /************************************************************************/
427 : /* GetNoDataValueAsUInt64() */
428 : /************************************************************************/
429 :
430 9 : uint64_t PDS4RawRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
431 : {
432 9 : if (pbSuccess)
433 9 : *pbSuccess = m_bHasNoDataUInt64;
434 9 : return m_nNoDataUInt64;
435 : }
436 :
437 : /************************************************************************/
438 : /* SetNoDataValueAsUInt64() */
439 : /************************************************************************/
440 :
441 3 : CPLErr PDS4RawRasterBand::SetNoDataValueAsUInt64(uint64_t nNewNoData)
442 : {
443 3 : m_nNoDataUInt64 = nNewNoData;
444 3 : m_bHasNoDataUInt64 = true;
445 :
446 3 : PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
447 3 : if (poGDS->m_poExternalDS && eAccess == GA_Update)
448 0 : poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValueAsUInt64(
449 0 : nNewNoData);
450 :
451 3 : return CE_None;
452 : }
453 :
454 : /************************************************************************/
455 : /* IReadBlock() */
456 : /************************************************************************/
457 :
458 132 : CPLErr PDS4RawRasterBand::IWriteBlock(int nXBlock, int nYBlock, void *pImage)
459 :
460 : {
461 132 : PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
462 132 : if (poGDS->m_bMustInitImageFile)
463 : {
464 0 : if (!poGDS->InitImageFile())
465 0 : return CE_Failure;
466 : }
467 :
468 132 : return RawRasterBand::IWriteBlock(nXBlock, nYBlock, pImage);
469 : }
470 :
471 : /************************************************************************/
472 : /* IRasterIO() */
473 : /************************************************************************/
474 :
475 1106 : CPLErr PDS4RawRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
476 : int nXSize, int nYSize, void *pData,
477 : int nBufXSize, int nBufYSize,
478 : GDALDataType eBufType, GSpacing nPixelSpace,
479 : GSpacing nLineSpace,
480 : GDALRasterIOExtraArg *psExtraArg)
481 :
482 : {
483 1106 : PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
484 1106 : if (eRWFlag == GF_Write && poGDS->m_bMustInitImageFile)
485 : {
486 10 : if (!poGDS->InitImageFile())
487 0 : return CE_Failure;
488 : }
489 :
490 1106 : return RawRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
491 : pData, nBufXSize, nBufYSize, eBufType,
492 1106 : nPixelSpace, nLineSpace, psExtraArg);
493 : }
494 :
495 : /************************************************************************/
496 : /* PDS4MaskBand() */
497 : /************************************************************************/
498 :
499 43 : PDS4MaskBand::PDS4MaskBand(GDALRasterBand *poBaseBand,
500 43 : const std::vector<double> &adfConstants)
501 43 : : m_poBaseBand(poBaseBand), m_pBuffer(nullptr), m_adfConstants(adfConstants)
502 : {
503 43 : eDataType = GDT_UInt8;
504 43 : poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
505 43 : nRasterXSize = poBaseBand->GetXSize();
506 43 : nRasterYSize = poBaseBand->GetYSize();
507 43 : }
508 :
509 : /************************************************************************/
510 : /* ~PDS4MaskBand() */
511 : /************************************************************************/
512 :
513 86 : PDS4MaskBand::~PDS4MaskBand()
514 : {
515 43 : VSIFree(m_pBuffer);
516 86 : }
517 :
518 : /************************************************************************/
519 : /* FillMask() */
520 : /************************************************************************/
521 :
522 : template <class T>
523 88 : static void FillMask(void *pvBuffer, GByte *pabyDst, int nReqXSize,
524 : int nReqYSize, int nBlockXSize,
525 : const std::vector<double> &adfConstants)
526 : {
527 88 : const T *pSrc = static_cast<T *>(pvBuffer);
528 176 : std::vector<T> aConstants;
529 248 : for (size_t i = 0; i < adfConstants.size(); i++)
530 : {
531 : T cst;
532 160 : GDALCopyWord(adfConstants[i], cst);
533 160 : aConstants.push_back(cst);
534 : }
535 :
536 176 : for (int y = 0; y < nReqYSize; y++)
537 : {
538 1696 : for (int x = 0; x < nReqXSize; x++)
539 : {
540 1608 : const T nSrc = pSrc[y * nBlockXSize + x];
541 1608 : if (std::find(aConstants.begin(), aConstants.end(), nSrc) !=
542 : aConstants.end())
543 : {
544 6 : pabyDst[y * nBlockXSize + x] = 0;
545 : }
546 : else
547 : {
548 1602 : pabyDst[y * nBlockXSize + x] = 255;
549 : }
550 : }
551 : }
552 88 : }
553 :
554 : /************************************************************************/
555 : /* IReadBlock() */
556 : /************************************************************************/
557 :
558 88 : CPLErr PDS4MaskBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
559 :
560 : {
561 88 : const GDALDataType eSrcDT = m_poBaseBand->GetRasterDataType();
562 88 : const int nSrcDTSize = GDALGetDataTypeSizeBytes(eSrcDT);
563 88 : if (m_pBuffer == nullptr)
564 : {
565 12 : m_pBuffer = VSI_MALLOC3_VERBOSE(nBlockXSize, nBlockYSize, nSrcDTSize);
566 12 : if (m_pBuffer == nullptr)
567 0 : return CE_Failure;
568 : }
569 :
570 88 : const int nXOff = nBlockXOff * nBlockXSize;
571 88 : const int nReqXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
572 88 : const int nYOff = nBlockYOff * nBlockYSize;
573 88 : const int nReqYSize = std::min(nBlockYSize, nRasterYSize - nYOff);
574 :
575 176 : if (m_poBaseBand->RasterIO(GF_Read, nXOff, nYOff, nReqXSize, nReqYSize,
576 : m_pBuffer, nReqXSize, nReqYSize, eSrcDT,
577 : nSrcDTSize,
578 88 : static_cast<GSpacing>(nSrcDTSize) * nBlockXSize,
579 88 : nullptr) != CE_None)
580 : {
581 0 : return CE_Failure;
582 : }
583 :
584 88 : GByte *pabyDst = static_cast<GByte *>(pImage);
585 88 : if (eSrcDT == GDT_UInt8)
586 : {
587 81 : FillMask<GByte>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
588 81 : m_adfConstants);
589 : }
590 7 : else if (eSrcDT == GDT_Int8)
591 : {
592 1 : FillMask<GInt8>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
593 1 : m_adfConstants);
594 : }
595 6 : else if (eSrcDT == GDT_UInt16)
596 : {
597 1 : FillMask<GUInt16>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
598 1 : m_adfConstants);
599 : }
600 5 : else if (eSrcDT == GDT_Int16)
601 : {
602 1 : FillMask<GInt16>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
603 1 : m_adfConstants);
604 : }
605 4 : else if (eSrcDT == GDT_UInt32)
606 : {
607 1 : FillMask<GUInt32>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
608 1 : m_adfConstants);
609 : }
610 3 : else if (eSrcDT == GDT_Int32)
611 : {
612 1 : FillMask<GInt32>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
613 1 : m_adfConstants);
614 : }
615 2 : else if (eSrcDT == GDT_UInt64)
616 : {
617 0 : FillMask<uint64_t>(m_pBuffer, pabyDst, nReqXSize, nReqYSize,
618 0 : nBlockXSize, m_adfConstants);
619 : }
620 2 : else if (eSrcDT == GDT_Int64)
621 : {
622 0 : FillMask<int64_t>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
623 0 : m_adfConstants);
624 : }
625 2 : else if (eSrcDT == GDT_Float32)
626 : {
627 1 : FillMask<float>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
628 1 : m_adfConstants);
629 : }
630 1 : else if (eSrcDT == GDT_Float64)
631 : {
632 1 : FillMask<double>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
633 1 : m_adfConstants);
634 : }
635 :
636 88 : return CE_None;
637 : }
638 :
639 : /************************************************************************/
640 : /* PDS4Dataset() */
641 : /************************************************************************/
642 :
643 490 : PDS4Dataset::PDS4Dataset()
644 : {
645 490 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
646 490 : }
647 :
648 : /************************************************************************/
649 : /* ~PDS4Dataset() */
650 : /************************************************************************/
651 :
652 980 : PDS4Dataset::~PDS4Dataset()
653 : {
654 490 : PDS4Dataset::Close();
655 980 : }
656 :
657 : /************************************************************************/
658 : /* Close() */
659 : /************************************************************************/
660 :
661 844 : CPLErr PDS4Dataset::Close(GDALProgressFunc, void *)
662 : {
663 844 : CPLErr eErr = CE_None;
664 844 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
665 : {
666 490 : if (m_bMustInitImageFile)
667 : {
668 72 : if (!InitImageFile())
669 0 : eErr = CE_Failure;
670 : }
671 :
672 490 : if (PDS4Dataset::FlushCache(true) != CE_None)
673 0 : eErr = CE_Failure;
674 :
675 490 : if (m_bCreateHeader || m_bDirtyHeader)
676 193 : WriteHeader();
677 490 : if (m_fpImage)
678 337 : VSIFCloseL(m_fpImage);
679 490 : CSLDestroy(m_papszCreationOptions);
680 490 : PDS4Dataset::CloseDependentDatasets();
681 :
682 490 : if (GDALPamDataset::Close() != CE_None)
683 0 : eErr = CE_Failure;
684 : }
685 844 : return eErr;
686 : }
687 :
688 : /************************************************************************/
689 : /* GetRawBinaryLayout() */
690 : /************************************************************************/
691 :
692 1 : bool PDS4Dataset::GetRawBinaryLayout(GDALDataset::RawBinaryLayout &sLayout)
693 : {
694 1 : if (!RawDataset::GetRawBinaryLayout(sLayout))
695 0 : return false;
696 1 : sLayout.osRawFilename = m_osImageFilename;
697 1 : return true;
698 : }
699 :
700 : /************************************************************************/
701 : /* CloseDependentDatasets() */
702 : /************************************************************************/
703 :
704 490 : int PDS4Dataset::CloseDependentDatasets()
705 : {
706 490 : int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
707 :
708 490 : if (m_poExternalDS)
709 : {
710 20 : bHasDroppedRef = FALSE;
711 20 : delete m_poExternalDS;
712 20 : m_poExternalDS = nullptr;
713 :
714 46 : for (int iBand = 0; iBand < nBands; iBand++)
715 : {
716 26 : delete papoBands[iBand];
717 26 : papoBands[iBand] = nullptr;
718 : }
719 20 : nBands = 0;
720 : }
721 :
722 490 : return bHasDroppedRef;
723 : }
724 :
725 : /************************************************************************/
726 : /* GetSpatialRef() */
727 : /************************************************************************/
728 :
729 46 : const OGRSpatialReference *PDS4Dataset::GetSpatialRef() const
730 : {
731 46 : if (!m_oSRS.IsEmpty())
732 41 : return &m_oSRS;
733 5 : return GDALPamDataset::GetSpatialRef();
734 : }
735 :
736 : /************************************************************************/
737 : /* SetSpatialRef() */
738 : /************************************************************************/
739 :
740 101 : CPLErr PDS4Dataset::SetSpatialRef(const OGRSpatialReference *poSRS)
741 :
742 : {
743 101 : if (eAccess == GA_ReadOnly)
744 0 : return CE_Failure;
745 101 : m_oSRS.Clear();
746 101 : if (poSRS)
747 101 : m_oSRS = *poSRS;
748 101 : if (m_poExternalDS)
749 10 : m_poExternalDS->SetSpatialRef(poSRS);
750 101 : return CE_None;
751 : }
752 :
753 : /************************************************************************/
754 : /* GetGeoTransform() */
755 : /************************************************************************/
756 :
757 50 : CPLErr PDS4Dataset::GetGeoTransform(GDALGeoTransform >) const
758 :
759 : {
760 50 : if (m_bGotTransform)
761 : {
762 47 : gt = m_gt;
763 47 : return CE_None;
764 : }
765 :
766 3 : return GDALPamDataset::GetGeoTransform(gt);
767 : }
768 :
769 : /************************************************************************/
770 : /* SetGeoTransform() */
771 : /************************************************************************/
772 :
773 101 : CPLErr PDS4Dataset::SetGeoTransform(const GDALGeoTransform >)
774 :
775 : {
776 101 : if (!((gt.xscale > 0.0 && gt.xrot == 0.0 && gt.yrot == 0.0 &&
777 100 : gt.yscale < 0.0) ||
778 1 : (gt.xscale == 0.0 && gt.xrot > 0.0 && gt.yrot > 0.0 &&
779 1 : gt.yscale == 0.0)))
780 : {
781 0 : CPLError(CE_Failure, CPLE_NotSupported,
782 : "Only north-up geotransform or map_projection_rotation=90 "
783 : "supported");
784 0 : return CE_Failure;
785 : }
786 101 : m_gt = gt;
787 101 : m_bGotTransform = true;
788 101 : if (m_poExternalDS)
789 10 : m_poExternalDS->SetGeoTransform(m_gt);
790 101 : return CE_None;
791 : }
792 :
793 : /************************************************************************/
794 : /* SetMetadata() */
795 : /************************************************************************/
796 :
797 8 : CPLErr PDS4Dataset::SetMetadata(CSLConstList papszMD, const char *pszDomain)
798 : {
799 8 : if (m_bUseSrcLabel && eAccess == GA_Update && pszDomain != nullptr &&
800 8 : EQUAL(pszDomain, "xml:PDS4"))
801 : {
802 6 : if (papszMD != nullptr && papszMD[0] != nullptr)
803 : {
804 6 : m_osXMLPDS4 = papszMD[0];
805 : }
806 6 : return CE_None;
807 : }
808 2 : return GDALPamDataset::SetMetadata(papszMD, pszDomain);
809 : }
810 :
811 : /************************************************************************/
812 : /* GetFileList() */
813 : /************************************************************************/
814 :
815 139 : char **PDS4Dataset::GetFileList()
816 : {
817 139 : char **papszFileList = GDALPamDataset::GetFileList();
818 276 : if (!m_osXMLFilename.empty() &&
819 137 : CSLFindString(papszFileList, m_osXMLFilename) < 0)
820 : {
821 0 : papszFileList = CSLAddString(papszFileList, m_osXMLFilename);
822 : }
823 139 : if (!m_osImageFilename.empty())
824 : {
825 90 : papszFileList = CSLAddString(papszFileList, m_osImageFilename);
826 : }
827 203 : for (const auto &poLayer : m_apoLayers)
828 : {
829 64 : auto papszTemp = poLayer->GetFileList();
830 64 : papszFileList = CSLInsertStrings(papszFileList, -1, papszTemp);
831 64 : CSLDestroy(papszTemp);
832 : }
833 139 : return papszFileList;
834 : }
835 :
836 : /************************************************************************/
837 : /* GetLinearValue() */
838 : /************************************************************************/
839 :
840 : static const struct
841 : {
842 : const char *pszUnit;
843 : double dfToMeter;
844 : } apsLinearUnits[] = {
845 : {"AU", 149597870700.0}, {"Angstrom", 1e-10}, {"cm", 1e-2}, {"km", 1e3},
846 : {"micrometer", 1e-6}, {"mm", 1e-3}, {"nm", 1e-9}};
847 :
848 802 : static double GetLinearValue(const CPLXMLNode *psParent,
849 : const char *pszElementName)
850 : {
851 802 : const CPLXMLNode *psNode = CPLGetXMLNode(psParent, pszElementName);
852 802 : if (psNode == nullptr)
853 0 : return 0.0;
854 802 : double dfVal = CPLAtof(CPLGetXMLValue(psNode, nullptr, ""));
855 802 : const char *pszUnit = CPLGetXMLValue(psNode, "unit", nullptr);
856 802 : if (pszUnit && !EQUAL(pszUnit, "m"))
857 : {
858 21 : bool bFound = false;
859 84 : for (size_t i = 0; i < CPL_ARRAYSIZE(apsLinearUnits); i++)
860 : {
861 84 : if (EQUAL(pszUnit, apsLinearUnits[i].pszUnit))
862 : {
863 21 : dfVal *= apsLinearUnits[i].dfToMeter;
864 21 : bFound = true;
865 21 : break;
866 : }
867 : }
868 21 : if (!bFound)
869 : {
870 0 : CPLError(CE_Warning, CPLE_AppDefined, "Unknown unit '%s' for '%s'",
871 : pszUnit, pszElementName);
872 : }
873 : }
874 802 : return dfVal;
875 : }
876 :
877 : /************************************************************************/
878 : /* GetResolutionValue() */
879 : /************************************************************************/
880 :
881 : static const struct
882 : {
883 : const char *pszUnit;
884 : double dfToMeter;
885 : } apsResolutionUnits[] = {
886 : {"km/pixel", 1e3},
887 : {"mm/pixel", 1e-3},
888 : };
889 :
890 316 : static double GetResolutionValue(CPLXMLNode *psParent,
891 : const char *pszElementName)
892 : {
893 316 : CPLXMLNode *psNode = CPLGetXMLNode(psParent, pszElementName);
894 316 : if (psNode == nullptr)
895 0 : return 0.0;
896 316 : double dfVal = CPLAtof(CPLGetXMLValue(psNode, nullptr, ""));
897 316 : const char *pszUnit = CPLGetXMLValue(psNode, "unit", nullptr);
898 316 : if (pszUnit && !EQUAL(pszUnit, "m/pixel"))
899 : {
900 21 : bool bFound = false;
901 21 : for (size_t i = 0; i < CPL_ARRAYSIZE(apsResolutionUnits); i++)
902 : {
903 21 : if (EQUAL(pszUnit, apsResolutionUnits[i].pszUnit))
904 : {
905 21 : dfVal *= apsResolutionUnits[i].dfToMeter;
906 21 : bFound = true;
907 21 : break;
908 : }
909 : }
910 21 : if (!bFound)
911 : {
912 0 : CPLError(CE_Warning, CPLE_AppDefined, "Unknown unit '%s' for '%s'",
913 : pszUnit, pszElementName);
914 : }
915 : }
916 316 : return dfVal;
917 : }
918 :
919 : /************************************************************************/
920 : /* GetAngularValue() */
921 : /************************************************************************/
922 :
923 : static const struct
924 : {
925 : const char *pszUnit;
926 : double dfToDeg;
927 : } apsAngularUnits[] = {{"arcmin", 1. / 60.},
928 : {"arcsec", 1. / 3600},
929 : {"hr", 15.0},
930 : {"mrad", 180.0 / M_PI / 1000.},
931 : {"rad", 180.0 / M_PI}};
932 :
933 819 : static double GetAngularValue(CPLXMLNode *psParent, const char *pszElementName,
934 : bool *pbGotVal = nullptr)
935 : {
936 819 : CPLXMLNode *psNode = CPLGetXMLNode(psParent, pszElementName);
937 819 : if (psNode == nullptr)
938 : {
939 424 : if (pbGotVal)
940 265 : *pbGotVal = false;
941 424 : return 0.0;
942 : }
943 395 : double dfVal = CPLAtof(CPLGetXMLValue(psNode, nullptr, ""));
944 395 : const char *pszUnit = CPLGetXMLValue(psNode, "unit", nullptr);
945 395 : if (pszUnit && !EQUAL(pszUnit, "deg"))
946 : {
947 0 : bool bFound = false;
948 0 : for (size_t i = 0; i < CPL_ARRAYSIZE(apsAngularUnits); i++)
949 : {
950 0 : if (EQUAL(pszUnit, apsAngularUnits[i].pszUnit))
951 : {
952 0 : dfVal *= apsAngularUnits[i].dfToDeg;
953 0 : bFound = true;
954 0 : break;
955 : }
956 : }
957 0 : if (!bFound)
958 : {
959 0 : CPLError(CE_Warning, CPLE_AppDefined, "Unknown unit '%s' for '%s'",
960 : pszUnit, pszElementName);
961 : }
962 : }
963 395 : if (pbGotVal)
964 220 : *pbGotVal = true;
965 395 : return dfVal;
966 : }
967 :
968 : /************************************************************************/
969 : /* ReadGeoreferencing() */
970 : /************************************************************************/
971 :
972 : // See https://pds.nasa.gov/pds4/cart/v1/PDS4_CART_1G00_1950.xsd, (GDAL 3.4)
973 : // https://pds.nasa.gov/pds4/cart/v1/PDS4_CART_1D00_1933.xsd,
974 : // https://raw.githubusercontent.com/nasa-pds-data-dictionaries/ldd-cart/master/build/1.B.0.0/PDS4_CART_1B00.xsd,
975 : // https://pds.nasa.gov/pds4/cart/v1/PDS4_CART_1700.xsd
976 : // and the corresponding .sch files
977 295 : void PDS4Dataset::ReadGeoreferencing(CPLXMLNode *psProduct)
978 : {
979 295 : CPLXMLNode *psCart = CPLGetXMLNode(
980 : psProduct, "Observation_Area.Discipline_Area.Cartography");
981 295 : if (psCart == nullptr)
982 : {
983 133 : CPLDebug("PDS4",
984 : "Did not find Observation_Area.Discipline_Area.Cartography");
985 133 : return;
986 : }
987 :
988 : // Bounding box: informative only
989 : CPLXMLNode *psBounding =
990 162 : CPLGetXMLNode(psCart, "Spatial_Domain.Bounding_Coordinates");
991 162 : if (psBounding)
992 : {
993 : const char *pszWest =
994 162 : CPLGetXMLValue(psBounding, "west_bounding_coordinate", nullptr);
995 : const char *pszEast =
996 162 : CPLGetXMLValue(psBounding, "east_bounding_coordinate", nullptr);
997 : const char *pszNorth =
998 162 : CPLGetXMLValue(psBounding, "north_bounding_coordinate", nullptr);
999 : const char *pszSouth =
1000 162 : CPLGetXMLValue(psBounding, "south_bounding_coordinate", nullptr);
1001 162 : if (pszWest)
1002 162 : CPLDebug("PDS4", "West: %s", pszWest);
1003 162 : if (pszEast)
1004 162 : CPLDebug("PDS4", "East: %s", pszEast);
1005 162 : if (pszNorth)
1006 162 : CPLDebug("PDS4", "North: %s", pszNorth);
1007 162 : if (pszSouth)
1008 162 : CPLDebug("PDS4", "South: %s", pszSouth);
1009 : }
1010 :
1011 : CPLXMLNode *psSR =
1012 162 : CPLGetXMLNode(psCart, "Spatial_Reference_Information.Horizontal_"
1013 : "Coordinate_System_Definition");
1014 162 : if (psSR == nullptr)
1015 : {
1016 0 : CPLDebug("PDS4", "Did not find Spatial_Reference_Information."
1017 : "Horizontal_Coordinate_System_Definition");
1018 0 : return;
1019 : }
1020 :
1021 162 : double dfLongitudeMultiplier = 1;
1022 162 : const CPLXMLNode *psGeodeticModel = CPLGetXMLNode(psSR, "Geodetic_Model");
1023 162 : if (psGeodeticModel != nullptr)
1024 : {
1025 162 : if (EQUAL(CPLGetXMLValue(psGeodeticModel, "longitude_direction", ""),
1026 : "Positive West"))
1027 : {
1028 3 : dfLongitudeMultiplier = -1;
1029 : }
1030 : }
1031 :
1032 324 : OGRSpatialReference oSRS;
1033 : CPLXMLNode *psGridCoordinateSystem =
1034 162 : CPLGetXMLNode(psSR, "Planar.Grid_Coordinate_System");
1035 162 : CPLXMLNode *psMapProjection = CPLGetXMLNode(psSR, "Planar.Map_Projection");
1036 324 : CPLString osProjName;
1037 162 : double dfCenterLon = 0.0;
1038 162 : double dfCenterLat = 0.0;
1039 162 : double dfStdParallel1 = 0.0;
1040 162 : double dfStdParallel2 = 0.0;
1041 162 : double dfScale = 1.0;
1042 162 : double dfMapProjectionRotation = 0.0;
1043 162 : if (psGridCoordinateSystem != nullptr)
1044 : {
1045 : osProjName = CPLGetXMLValue(psGridCoordinateSystem,
1046 0 : "grid_coordinate_system_name", "");
1047 0 : if (!osProjName.empty())
1048 : {
1049 0 : if (osProjName == "Universal Transverse Mercator")
1050 : {
1051 0 : CPLXMLNode *psUTMZoneNumber = CPLGetXMLNode(
1052 : psGridCoordinateSystem,
1053 : "Universal_Transverse_Mercator.utm_zone_number");
1054 0 : if (psUTMZoneNumber)
1055 : {
1056 : int nZone =
1057 0 : atoi(CPLGetXMLValue(psUTMZoneNumber, nullptr, ""));
1058 0 : oSRS.SetUTM(std::abs(nZone), nZone >= 0);
1059 : }
1060 : }
1061 0 : else if (osProjName == "Universal Polar Stereographic")
1062 : {
1063 0 : CPLXMLNode *psProjParamNode = CPLGetXMLNode(
1064 : psGridCoordinateSystem,
1065 : "Universal_Polar_Stereographic.Polar_Stereographic");
1066 0 : if (psProjParamNode)
1067 : {
1068 0 : dfCenterLon =
1069 0 : GetAngularValue(psProjParamNode,
1070 : "longitude_of_central_meridian") *
1071 : dfLongitudeMultiplier;
1072 0 : dfCenterLat = GetAngularValue(
1073 : psProjParamNode, "latitude_of_projection_origin");
1074 0 : dfScale = CPLAtof(CPLGetXMLValue(
1075 : psProjParamNode, "scale_factor_at_projection_origin",
1076 : "1"));
1077 0 : oSRS.SetPS(dfCenterLat, dfCenterLon, dfScale, 0, 0);
1078 : }
1079 : }
1080 : else
1081 : {
1082 0 : CPLError(CE_Warning, CPLE_NotSupported,
1083 : "grid_coordinate_system_name = %s not supported",
1084 : osProjName.c_str());
1085 : }
1086 : }
1087 : }
1088 162 : else if (psMapProjection != nullptr)
1089 : {
1090 161 : osProjName = CPLGetXMLValue(psMapProjection, "map_projection_name", "");
1091 161 : if (!osProjName.empty())
1092 : {
1093 161 : CPLXMLNode *psProjParamNode = CPLGetXMLNode(
1094 : psMapProjection,
1095 322 : CPLString(osProjName).replaceAll(' ', '_').c_str());
1096 161 : if (psProjParamNode == nullptr &&
1097 : // typo in https://pds.nasa.gov/pds4/cart/v1/PDS4_CART_1700.sch
1098 0 : EQUAL(osProjName, "Orothographic"))
1099 : {
1100 : psProjParamNode =
1101 0 : CPLGetXMLNode(psMapProjection, "Orthographic");
1102 : }
1103 161 : bool bGotStdParallel1 = false;
1104 161 : bool bGotStdParallel2 = false;
1105 161 : bool bGotScale = false;
1106 161 : if (psProjParamNode)
1107 : {
1108 161 : bool bGotCenterLon = false;
1109 161 : dfCenterLon = GetAngularValue(psProjParamNode,
1110 : "longitude_of_central_meridian",
1111 : &bGotCenterLon) *
1112 : dfLongitudeMultiplier;
1113 161 : if (!bGotCenterLon)
1114 : {
1115 2 : dfCenterLon =
1116 2 : GetAngularValue(psProjParamNode,
1117 : "straight_vertical_longitude_from_pole",
1118 : &bGotCenterLon) *
1119 : dfLongitudeMultiplier;
1120 : }
1121 161 : dfCenterLat = GetAngularValue(psProjParamNode,
1122 : "latitude_of_projection_origin");
1123 161 : dfStdParallel1 = GetAngularValue(
1124 : psProjParamNode, "standard_parallel_1", &bGotStdParallel1);
1125 161 : dfStdParallel2 = GetAngularValue(
1126 : psProjParamNode, "standard_parallel_2", &bGotStdParallel2);
1127 : const char *pszScaleParam =
1128 161 : (osProjName == "Transverse Mercator")
1129 161 : ? "scale_factor_at_central_meridian"
1130 161 : : "scale_factor_at_projection_origin";
1131 : const char *pszScaleVal =
1132 161 : CPLGetXMLValue(psProjParamNode, pszScaleParam, nullptr);
1133 161 : bGotScale = pszScaleVal != nullptr;
1134 161 : dfScale = (pszScaleVal) ? CPLAtof(pszScaleVal) : 1.0;
1135 :
1136 : dfMapProjectionRotation =
1137 161 : GetAngularValue(psProjParamNode, "map_projection_rotation");
1138 : }
1139 :
1140 : CPLXMLNode *psObliqueAzimuth =
1141 161 : CPLGetXMLNode(psProjParamNode, "Oblique_Line_Azimuth");
1142 : CPLXMLNode *psObliquePoint =
1143 161 : CPLGetXMLNode(psProjParamNode, "Oblique_Line_Point");
1144 :
1145 161 : if (EQUAL(osProjName, "Equirectangular"))
1146 : {
1147 53 : oSRS.SetEquirectangular2(dfCenterLat, dfCenterLon,
1148 : dfStdParallel1, 0, 0);
1149 : }
1150 108 : else if (EQUAL(osProjName, "Lambert Conformal Conic"))
1151 : {
1152 4 : if (bGotScale)
1153 : {
1154 2 : if ((bGotStdParallel1 && dfStdParallel1 != dfCenterLat) ||
1155 0 : (bGotStdParallel2 && dfStdParallel2 != dfCenterLat))
1156 : {
1157 0 : CPLError(
1158 : CE_Warning, CPLE_AppDefined,
1159 : "Ignoring standard_parallel_1 and/or "
1160 : "standard_parallel_2 with LCC_1SP formulation");
1161 : }
1162 2 : oSRS.SetLCC1SP(dfCenterLat, dfCenterLon, dfScale, 0, 0);
1163 : }
1164 : else
1165 : {
1166 2 : oSRS.SetLCC(dfStdParallel1, dfStdParallel2, dfCenterLat,
1167 : dfCenterLon, 0, 0);
1168 : }
1169 : }
1170 104 : else if (EQUAL(osProjName, "Mercator"))
1171 : {
1172 6 : if (bGotScale)
1173 : {
1174 4 : oSRS.SetMercator(dfCenterLat, // should be 0 normally
1175 : dfCenterLon, dfScale, 0.0, 0.0);
1176 : }
1177 : else
1178 : {
1179 2 : oSRS.SetMercator2SP(dfStdParallel1,
1180 : dfCenterLat, // should be 0 normally
1181 : dfCenterLon, 0.0, 0.0);
1182 : }
1183 : }
1184 98 : else if (EQUAL(osProjName, "Orthographic"))
1185 : {
1186 2 : oSRS.SetOrthographic(dfCenterLat, dfCenterLon, 0.0, 0.0);
1187 : }
1188 98 : else if (EQUAL(osProjName, "Oblique Mercator") &&
1189 2 : (psObliqueAzimuth != nullptr || psObliquePoint != nullptr))
1190 : {
1191 4 : if (psObliqueAzimuth)
1192 : {
1193 : // Not sure of this
1194 2 : dfCenterLon = CPLAtof(
1195 : CPLGetXMLValue(psObliqueAzimuth,
1196 : "azimuth_measure_point_longitude", "0"));
1197 :
1198 2 : double dfAzimuth = CPLAtof(CPLGetXMLValue(
1199 : psObliqueAzimuth, "azimuthal_angle", "0"));
1200 2 : oSRS.SetProjection(
1201 : SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER);
1202 2 : oSRS.SetNormProjParm(SRS_PP_LATITUDE_OF_CENTER,
1203 : dfCenterLat);
1204 2 : oSRS.SetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER,
1205 : dfCenterLon);
1206 2 : oSRS.SetNormProjParm(SRS_PP_AZIMUTH, dfAzimuth);
1207 : // SetNormProjParm( SRS_PP_RECTIFIED_GRID_ANGLE,
1208 : // dfRectToSkew );
1209 2 : oSRS.SetNormProjParm(SRS_PP_SCALE_FACTOR, dfScale);
1210 2 : oSRS.SetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
1211 2 : oSRS.SetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
1212 : }
1213 : else
1214 : {
1215 2 : double dfLat1 = 0.0;
1216 2 : double dfLong1 = 0.0;
1217 2 : double dfLat2 = 0.0;
1218 2 : double dfLong2 = 0.0;
1219 2 : CPLXMLNode *psPoint = CPLGetXMLNode(
1220 : psObliquePoint, "Oblique_Line_Point_Group");
1221 2 : if (psPoint)
1222 : {
1223 2 : dfLat1 = CPLAtof(CPLGetXMLValue(
1224 : psPoint, "oblique_line_latitude", "0.0"));
1225 2 : dfLong1 = CPLAtof(CPLGetXMLValue(
1226 : psPoint, "oblique_line_longitude", "0.0"));
1227 2 : psPoint = psPoint->psNext;
1228 2 : if (psPoint && psPoint->eType == CXT_Element &&
1229 2 : EQUAL(psPoint->pszValue,
1230 : "Oblique_Line_Point_Group"))
1231 : {
1232 2 : dfLat2 = CPLAtof(CPLGetXMLValue(
1233 : psPoint, "oblique_line_latitude", "0.0"));
1234 2 : dfLong2 = CPLAtof(CPLGetXMLValue(
1235 : psPoint, "oblique_line_longitude", "0.0"));
1236 : }
1237 : }
1238 2 : oSRS.SetHOM2PNO(dfCenterLat, dfLat1, dfLong1, dfLat2,
1239 : dfLong2, dfScale, 0.0, 0.0);
1240 : }
1241 : }
1242 92 : else if (EQUAL(osProjName, "Polar Stereographic"))
1243 : {
1244 2 : oSRS.SetPS(dfCenterLat, dfCenterLon, dfScale, 0, 0);
1245 : }
1246 90 : else if (EQUAL(osProjName, "Polyconic"))
1247 : {
1248 2 : oSRS.SetPolyconic(dfCenterLat, dfCenterLon, 0, 0);
1249 : }
1250 88 : else if (EQUAL(osProjName, "Sinusoidal"))
1251 : {
1252 4 : oSRS.SetSinusoidal(dfCenterLon, 0, 0);
1253 : }
1254 84 : else if (EQUAL(osProjName, "Transverse Mercator"))
1255 : {
1256 78 : oSRS.SetTM(dfCenterLat, dfCenterLon, dfScale, 0, 0);
1257 : }
1258 :
1259 : // Below values are valid map_projection_name according to
1260 : // the schematron but they don't have a dedicated element to
1261 : // hold the projection parameter. Assumed the schema is extended
1262 : // similarly to the existing for a few obvious ones
1263 6 : else if (EQUAL(osProjName, "Albers Conical Equal Area"))
1264 : {
1265 0 : oSRS.SetACEA(dfStdParallel1, dfStdParallel2, dfCenterLat,
1266 : dfCenterLon, 0.0, 0.0);
1267 : }
1268 6 : else if (EQUAL(osProjName, "Azimuthal Equidistant"))
1269 : {
1270 0 : oSRS.SetAE(dfCenterLat, dfCenterLon, 0, 0);
1271 : }
1272 6 : else if (EQUAL(osProjName, "Equidistant Conic"))
1273 : {
1274 0 : oSRS.SetEC(dfStdParallel1, dfStdParallel2, dfCenterLat,
1275 : dfCenterLon, 0.0, 0.0);
1276 : }
1277 : // Unhandled: General Vertical Near-sided Projection
1278 6 : else if (EQUAL(osProjName, "Gnomonic"))
1279 : {
1280 0 : oSRS.SetGnomonic(dfCenterLat, dfCenterLon, 0, 0);
1281 : }
1282 6 : else if (EQUAL(osProjName, "Lambert Azimuthal Equal Area"))
1283 : {
1284 2 : oSRS.SetLAEA(dfCenterLat, dfCenterLon, 0, 0);
1285 : }
1286 4 : else if (EQUAL(osProjName, "Miller Cylindrical"))
1287 : {
1288 0 : oSRS.SetMC(dfCenterLat, dfCenterLon, 0, 0);
1289 : }
1290 4 : else if (EQUAL(osProjName, "Orothographic") // typo
1291 4 : || EQUAL(osProjName, "Orthographic"))
1292 : {
1293 0 : osProjName = "Orthographic";
1294 0 : oSRS.SetOrthographic(dfCenterLat, dfCenterLon, 0, 0);
1295 : }
1296 4 : else if (EQUAL(osProjName, "Robinson"))
1297 : {
1298 0 : oSRS.SetRobinson(dfCenterLon, 0, 0);
1299 : }
1300 : // Unhandled: Space Oblique Mercator
1301 4 : else if (EQUAL(osProjName, "Stereographic"))
1302 : {
1303 0 : oSRS.SetStereographic(dfCenterLat, dfCenterLon, dfScale, 0, 0);
1304 : }
1305 4 : else if (EQUAL(osProjName, "van der Grinten"))
1306 : {
1307 0 : oSRS.SetVDG(dfCenterLon, 0, 0);
1308 : }
1309 4 : else if (EQUAL(osProjName, "Oblique Cylindrical"))
1310 : {
1311 4 : const double poleLatitude = GetAngularValue(
1312 : psProjParamNode, "oblique_proj_pole_latitude");
1313 : const double poleLongitude =
1314 4 : GetAngularValue(psProjParamNode,
1315 : "oblique_proj_pole_longitude") *
1316 4 : dfLongitudeMultiplier;
1317 4 : const double poleRotation = GetAngularValue(
1318 : psProjParamNode, "oblique_proj_pole_rotation");
1319 :
1320 8 : CPLString oProj4String;
1321 : // Cf isis3dataset.cpp comments for ObliqueCylindrical
1322 : oProj4String.Printf("+proj=ob_tran +o_proj=eqc +o_lon_p=%.17g "
1323 : "+o_lat_p=%.17g +lon_0=%.17g",
1324 : -poleRotation, 180 - poleLatitude,
1325 4 : poleLongitude);
1326 4 : oSRS.SetFromUserInput(oProj4String);
1327 : }
1328 : else
1329 : {
1330 0 : CPLError(CE_Warning, CPLE_NotSupported,
1331 : "map_projection_name = %s not supported",
1332 : osProjName.c_str());
1333 : }
1334 : }
1335 : }
1336 : else
1337 : {
1338 1 : CPLXMLNode *psGeographic = CPLGetXMLNode(psSR, "Geographic");
1339 1 : if (GetLayerCount() && psGeographic)
1340 : {
1341 : // do nothing
1342 : }
1343 : else
1344 : {
1345 0 : CPLError(CE_Warning, CPLE_AppDefined,
1346 : "Planar.Map_Projection not found");
1347 : }
1348 : }
1349 :
1350 162 : if (oSRS.IsProjected())
1351 : {
1352 161 : oSRS.SetLinearUnits("Metre", 1.0);
1353 : }
1354 :
1355 162 : if (psGeodeticModel != nullptr)
1356 : {
1357 : const char *pszLatitudeType =
1358 162 : CPLGetXMLValue(psGeodeticModel, "latitude_type", "");
1359 162 : bool bIsOgraphic = EQUAL(pszLatitudeType, "Planetographic");
1360 :
1361 : const bool bUseLDD1930RadiusNames =
1362 162 : CPLGetXMLNode(psGeodeticModel, "a_axis_radius") != nullptr;
1363 :
1364 : // Before PDS CART schema pre-1.B.10.0 (pre LDD version 1.9.3.0),
1365 : // the confusing semi_major_radius, semi_minor_radius and polar_radius
1366 : // were used but did not follow the recommended
1367 : // FGDC names. Using both "semi" and "radius" in the same keyword,
1368 : // which both mean half, does not make sense.
1369 162 : const char *pszAAxis =
1370 162 : bUseLDD1930RadiusNames ? "a_axis_radius" : "semi_major_radius";
1371 162 : const char *pszBAxis =
1372 162 : bUseLDD1930RadiusNames ? "b_axis_radius" : "semi_minor_radius";
1373 162 : const char *pszCAxis =
1374 162 : bUseLDD1930RadiusNames ? "c_axis_radius" : "polar_radius";
1375 :
1376 162 : const double dfSemiMajor = GetLinearValue(psGeodeticModel, pszAAxis);
1377 :
1378 : // a_axis_radius and b_axis_radius should be the same in most cases
1379 : // unless a triaxial body is being defined. This should be extremely
1380 : // rare (and not used) since the IAU generally defines a best-fit sphere
1381 : // for triaxial bodies: https://astrogeology.usgs.gov/groups/IAU-WGCCRE
1382 162 : const double dfBValue = GetLinearValue(psGeodeticModel, pszBAxis);
1383 162 : if (dfSemiMajor != dfBValue)
1384 : {
1385 0 : CPLError(CE_Warning, CPLE_AppDefined,
1386 : "%s = %f m, different from "
1387 : "%s = %f, will be ignored",
1388 : pszBAxis, dfBValue, pszAAxis, dfSemiMajor);
1389 : }
1390 :
1391 162 : const double dfPolarRadius = GetLinearValue(psGeodeticModel, pszCAxis);
1392 : // Use the polar_radius as the actual semi minor
1393 162 : const double dfSemiMinor = dfPolarRadius;
1394 :
1395 : // Compulsory
1396 162 : const char *pszTargetName = CPLGetXMLValue(
1397 : psProduct, "Observation_Area.Target_Identification.name",
1398 : "unknown");
1399 :
1400 162 : if (oSRS.IsProjected())
1401 : {
1402 483 : CPLString osProjTargetName = osProjName + " " + pszTargetName;
1403 161 : oSRS.SetProjCS(osProjTargetName);
1404 : }
1405 :
1406 486 : CPLString osGeogName = CPLString("GCS_") + pszTargetName;
1407 :
1408 : CPLString osSphereName =
1409 324 : CPLGetXMLValue(psGeodeticModel, "spheroid_name", pszTargetName);
1410 324 : CPLString osDatumName = "D_" + osSphereName;
1411 :
1412 : // calculate inverse flattening from major and minor axis: 1/f = a/(a-b)
1413 162 : double dfInvFlattening = 0;
1414 162 : if ((dfSemiMajor - dfSemiMinor) >= 0.00000001)
1415 : {
1416 82 : dfInvFlattening = dfSemiMajor / (dfSemiMajor - dfSemiMinor);
1417 : }
1418 :
1419 : //(if stereographic with center lat ==90) or (polar stereographic )
1420 324 : if ((EQUAL(osProjName, "STEREOGRAPHIC") && fabs(dfCenterLat) == 90) ||
1421 162 : (EQUAL(osProjName, "POLAR STEREOGRAPHIC")))
1422 : {
1423 2 : if (bIsOgraphic)
1424 : {
1425 0 : oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName,
1426 : dfSemiMajor, dfInvFlattening,
1427 : "Reference_Meridian", 0.0);
1428 : }
1429 : else
1430 : {
1431 2 : osSphereName += "_polarRadius";
1432 2 : oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName,
1433 : dfPolarRadius, 0.0, "Reference_Meridian", 0.0);
1434 : }
1435 : }
1436 160 : else if ((EQUAL(osProjName, "EQUIRECTANGULAR")) ||
1437 107 : (EQUAL(osProjName, "ORTHOGRAPHIC")) ||
1438 372 : (EQUAL(osProjName, "STEREOGRAPHIC")) ||
1439 105 : (EQUAL(osProjName, "SINUSOIDAL")))
1440 : {
1441 59 : oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName, dfSemiMajor,
1442 : 0.0, "Reference_Meridian", 0.0);
1443 : }
1444 : else
1445 : {
1446 101 : if (bIsOgraphic)
1447 : {
1448 2 : oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName,
1449 : dfSemiMajor, dfInvFlattening,
1450 : "Reference_Meridian", 0.0);
1451 : }
1452 : else
1453 : {
1454 99 : oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName,
1455 : dfSemiMajor, 0.0, "Reference_Meridian", 0.0);
1456 : }
1457 : }
1458 : }
1459 :
1460 : CPLXMLNode *psPCI =
1461 162 : CPLGetXMLNode(psSR, "Planar.Planar_Coordinate_Information");
1462 162 : CPLXMLNode *psGT = CPLGetXMLNode(psSR, "Planar.Geo_Transformation");
1463 162 : if (psPCI && psGT)
1464 : {
1465 : const char *pszPCIEncoding =
1466 158 : CPLGetXMLValue(psPCI, "planar_coordinate_encoding_method", "");
1467 158 : CPLXMLNode *psCR = CPLGetXMLNode(psPCI, "Coordinate_Representation");
1468 158 : if (!EQUAL(pszPCIEncoding, "Coordinate Pair"))
1469 : {
1470 0 : CPLError(CE_Warning, CPLE_NotSupported,
1471 : "planar_coordinate_encoding_method = %s not supported",
1472 : pszPCIEncoding);
1473 : }
1474 158 : else if (psCR != nullptr)
1475 : {
1476 158 : double dfXRes = GetResolutionValue(psCR, "pixel_resolution_x");
1477 158 : double dfYRes = GetResolutionValue(psCR, "pixel_resolution_y");
1478 158 : double dfULX = GetLinearValue(psGT, "upperleft_corner_x");
1479 158 : double dfULY = GetLinearValue(psGT, "upperleft_corner_y");
1480 :
1481 : // The PDS4 specification is not really clear about the
1482 : // origin convention, but it appears from
1483 : // https://github.com/OSGeo/gdal/issues/735 that it matches GDAL
1484 : // top-left corner of top-left pixel
1485 158 : m_gt.xorig = dfULX;
1486 158 : m_gt.xscale = dfXRes;
1487 158 : m_gt.xrot = 0.0;
1488 158 : m_gt.yorig = dfULY;
1489 158 : m_gt.yrot = 0.0;
1490 158 : m_gt.yscale = -dfYRes;
1491 158 : m_bGotTransform = true;
1492 :
1493 158 : if (dfMapProjectionRotation != 0)
1494 : {
1495 4 : const double sin_rot =
1496 : dfMapProjectionRotation == 90
1497 4 : ? 1.0
1498 0 : : sin(dfMapProjectionRotation / 180 * M_PI);
1499 4 : const double cos_rot =
1500 : dfMapProjectionRotation == 90
1501 4 : ? 0.0
1502 0 : : cos(dfMapProjectionRotation / 180 * M_PI);
1503 4 : const double gt_1 = cos_rot * m_gt.xscale - sin_rot * m_gt.yrot;
1504 4 : const double gt_2 = cos_rot * m_gt.xrot - sin_rot * m_gt.yscale;
1505 4 : const double gt_0 = cos_rot * m_gt.xorig - sin_rot * m_gt.yorig;
1506 4 : const double gt_4 = sin_rot * m_gt.xscale + cos_rot * m_gt.yrot;
1507 4 : const double gt_5 = sin_rot * m_gt.xrot + cos_rot * m_gt.yscale;
1508 4 : const double gt_3 = sin_rot * m_gt.xorig + cos_rot * m_gt.yorig;
1509 4 : m_gt.xscale = gt_1;
1510 4 : m_gt.xrot = gt_2;
1511 4 : m_gt.xorig = gt_0;
1512 4 : m_gt.yrot = gt_4;
1513 4 : m_gt.yscale = gt_5;
1514 4 : m_gt.yorig = gt_3;
1515 : }
1516 : }
1517 : }
1518 :
1519 162 : if (!oSRS.IsEmpty())
1520 : {
1521 162 : if (GetRasterCount())
1522 : {
1523 158 : m_oSRS = std::move(oSRS);
1524 : }
1525 4 : else if (GetLayerCount())
1526 : {
1527 8 : for (auto &poLayer : m_apoLayers)
1528 : {
1529 4 : if (poLayer->GetGeomType() != wkbNone)
1530 : {
1531 4 : auto poSRSClone = oSRS.Clone();
1532 4 : poLayer->SetSpatialRef(poSRSClone);
1533 4 : poSRSClone->Release();
1534 : }
1535 : }
1536 : }
1537 : }
1538 : }
1539 :
1540 : /************************************************************************/
1541 : /* GetLayer() */
1542 : /************************************************************************/
1543 :
1544 319 : const OGRLayer *PDS4Dataset::GetLayer(int nIndex) const
1545 : {
1546 319 : if (nIndex < 0 || nIndex >= GetLayerCount())
1547 8 : return nullptr;
1548 311 : return dynamic_cast<const OGRLayer *>(m_apoLayers[nIndex].get());
1549 : }
1550 :
1551 : /************************************************************************/
1552 : /* FixupTableFilename() */
1553 : /************************************************************************/
1554 :
1555 97 : static std::string FixupTableFilename(const std::string &osFilename)
1556 : {
1557 : VSIStatBufL sStat;
1558 97 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
1559 : {
1560 97 : return osFilename;
1561 : }
1562 0 : const std::string osExt = CPLGetExtensionSafe(osFilename.c_str());
1563 0 : if (!osExt.empty())
1564 : {
1565 0 : std::string osTry(osFilename);
1566 0 : if (osExt[0] >= 'a' && osExt[0] <= 'z')
1567 : {
1568 0 : osTry = CPLResetExtensionSafe(osFilename.c_str(),
1569 0 : CPLString(osExt).toupper());
1570 : }
1571 : else
1572 : {
1573 0 : osTry = CPLResetExtensionSafe(osFilename.c_str(),
1574 0 : CPLString(osExt).tolower());
1575 : }
1576 0 : if (VSIStatL(osTry.c_str(), &sStat) == 0)
1577 : {
1578 0 : return osTry;
1579 : }
1580 : }
1581 0 : return osFilename;
1582 : }
1583 :
1584 : /************************************************************************/
1585 : /* OpenTableCharacter() */
1586 : /************************************************************************/
1587 :
1588 22 : bool PDS4Dataset::OpenTableCharacter(const char *pszFilename,
1589 : const CPLXMLNode *psTable)
1590 : {
1591 22 : if (CPLHasPathTraversal(pszFilename))
1592 : {
1593 0 : CPLError(CE_Failure, CPLE_NotSupported,
1594 : "OpenTableCharacter(): path traversal detected: %s",
1595 : pszFilename);
1596 0 : return false;
1597 : }
1598 44 : std::string osLayerName(CPLGetBasenameSafe(pszFilename));
1599 22 : if (cpl::starts_with(osLayerName,
1600 44 : CPLGetBasenameSafe(m_osXMLFilename.c_str()) + "_"))
1601 26 : osLayerName = osLayerName.substr(
1602 39 : CPLGetBasenameSafe(m_osXMLFilename.c_str()).size() + 1);
1603 :
1604 44 : const std::string osFullFilename = FixupTableFilename(CPLFormFilenameSafe(
1605 66 : CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(), pszFilename, nullptr));
1606 : auto poLayer = std::make_unique<PDS4TableCharacter>(
1607 22 : this, osLayerName.c_str(), osFullFilename.c_str(),
1608 66 : eAccess == GA_Update);
1609 22 : if (!poLayer->ReadTableDef(psTable))
1610 : {
1611 0 : return false;
1612 : }
1613 22 : if (eAccess == GA_Update)
1614 : {
1615 6 : m_apoLayers.push_back(
1616 12 : std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
1617 : }
1618 : else
1619 : {
1620 16 : m_apoLayers.push_back(std::move(poLayer));
1621 : }
1622 22 : return true;
1623 : }
1624 :
1625 : /************************************************************************/
1626 : /* OpenTableBinary() */
1627 : /************************************************************************/
1628 :
1629 11 : bool PDS4Dataset::OpenTableBinary(const char *pszFilename,
1630 : const CPLXMLNode *psTable)
1631 : {
1632 11 : if (CPLHasPathTraversal(pszFilename))
1633 : {
1634 0 : CPLError(CE_Failure, CPLE_NotSupported,
1635 : "OpenTableBinary(): path traversal detected: %s", pszFilename);
1636 0 : return false;
1637 : }
1638 :
1639 22 : std::string osLayerName(CPLGetBasenameSafe(pszFilename));
1640 11 : if (cpl::starts_with(osLayerName,
1641 22 : CPLGetBasenameSafe(m_osXMLFilename.c_str()) + "_"))
1642 20 : osLayerName = osLayerName.substr(
1643 30 : CPLGetBasenameSafe(m_osXMLFilename.c_str()).size() + 1);
1644 :
1645 22 : const std::string osFullFilename = FixupTableFilename(CPLFormFilenameSafe(
1646 33 : CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(), pszFilename, nullptr));
1647 0 : auto poLayer = std::make_unique<PDS4TableBinary>(this, osLayerName.c_str(),
1648 11 : osFullFilename.c_str(),
1649 33 : eAccess == GA_Update);
1650 11 : if (!poLayer->ReadTableDef(psTable))
1651 : {
1652 0 : return false;
1653 : }
1654 11 : if (eAccess == GA_Update)
1655 : {
1656 1 : m_apoLayers.push_back(
1657 2 : std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
1658 : }
1659 : else
1660 : {
1661 10 : m_apoLayers.push_back(std::move(poLayer));
1662 : }
1663 11 : return true;
1664 : }
1665 :
1666 : /************************************************************************/
1667 : /* OpenTableDelimited() */
1668 : /************************************************************************/
1669 :
1670 64 : bool PDS4Dataset::OpenTableDelimited(const char *pszFilename,
1671 : const CPLXMLNode *psTable)
1672 : {
1673 64 : if (CPLHasPathTraversal(pszFilename))
1674 : {
1675 0 : CPLError(CE_Failure, CPLE_NotSupported,
1676 : "OpenTableDelimited(): path traversal detected: %s",
1677 : pszFilename);
1678 0 : return false;
1679 : }
1680 :
1681 128 : std::string osLayerName(CPLGetBasenameSafe(pszFilename));
1682 64 : if (cpl::starts_with(osLayerName,
1683 128 : CPLGetBasenameSafe(m_osXMLFilename.c_str()) + "_"))
1684 120 : osLayerName = osLayerName.substr(
1685 180 : CPLGetBasenameSafe(m_osXMLFilename.c_str()).size() + 1);
1686 :
1687 128 : const std::string osFullFilename = FixupTableFilename(CPLFormFilenameSafe(
1688 192 : CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(), pszFilename, nullptr));
1689 : auto poLayer = std::make_unique<PDS4DelimitedTable>(
1690 64 : this, osLayerName.c_str(), osFullFilename.c_str(),
1691 192 : eAccess == GA_Update);
1692 64 : if (!poLayer->ReadTableDef(psTable))
1693 : {
1694 0 : return false;
1695 : }
1696 64 : if (eAccess == GA_Update)
1697 : {
1698 2 : m_apoLayers.push_back(
1699 4 : std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
1700 : }
1701 : else
1702 : {
1703 62 : m_apoLayers.push_back(std::move(poLayer));
1704 : }
1705 64 : return true;
1706 : }
1707 :
1708 : /************************************************************************/
1709 : /* ConstantToDouble() */
1710 : /************************************************************************/
1711 :
1712 180 : static std::optional<double> ConstantToDouble(const char *pszItem,
1713 : const char *pszVal)
1714 : {
1715 180 : if (STARTS_WITH(pszVal, "0x"))
1716 : {
1717 21 : if (strlen(pszVal) == strlen("0x") + 2 * sizeof(float))
1718 : {
1719 15 : char *endptr = nullptr;
1720 : const uint32_t nVal =
1721 15 : static_cast<uint32_t>(std::strtoull(pszVal, &endptr, 0));
1722 15 : if (endptr == pszVal + strlen(pszVal))
1723 : {
1724 : float fVal;
1725 15 : memcpy(&fVal, &nVal, sizeof(nVal));
1726 15 : return fVal;
1727 : }
1728 : }
1729 6 : else if (strlen(pszVal) == strlen("0x") + 2 * sizeof(double))
1730 : {
1731 6 : char *endptr = nullptr;
1732 : const uint64_t nVal =
1733 6 : static_cast<uint64_t>(std::strtoull(pszVal, &endptr, 0));
1734 6 : if (endptr == pszVal + strlen(pszVal))
1735 : {
1736 : double dfVal;
1737 6 : memcpy(&dfVal, &nVal, sizeof(nVal));
1738 6 : return dfVal;
1739 : }
1740 : }
1741 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for '%s': '%s'",
1742 : pszItem, pszVal);
1743 0 : return std::nullopt;
1744 : }
1745 : else
1746 : {
1747 159 : char *endptr = nullptr;
1748 159 : double dfVal = std::strtod(pszVal, &endptr);
1749 159 : if (endptr == pszVal + strlen(pszVal))
1750 : {
1751 159 : return dfVal;
1752 : }
1753 : else
1754 : {
1755 0 : CPLError(CE_Failure, CPLE_AppDefined,
1756 : "Invalid value for '%s': '%s'", pszItem, pszVal);
1757 0 : return std::nullopt;
1758 : }
1759 : }
1760 : }
1761 :
1762 : /************************************************************************/
1763 : /* OpenBrowse() */
1764 : /************************************************************************/
1765 :
1766 : /* static */ std::unique_ptr<PDS4Dataset>
1767 2 : PDS4Dataset::OpenBrowse(GDALOpenInfo *poOpenInfo, const CPLXMLNode *psProduct)
1768 : {
1769 : const CPLXMLNode *psFileAreaBrowse =
1770 2 : CPLGetXMLNode(psProduct, "File_Area_Browse");
1771 2 : if (!psFileAreaBrowse)
1772 0 : return nullptr;
1773 : const char *pszFilename =
1774 2 : CPLGetXMLValue(psFileAreaBrowse, "File.file_name", nullptr);
1775 2 : if (!pszFilename)
1776 0 : return nullptr;
1777 2 : if (CPLHasPathTraversal(pszFilename))
1778 : {
1779 0 : CPLError(CE_Failure, CPLE_NotSupported, "Path traversal detected in %s",
1780 : pszFilename);
1781 0 : return nullptr;
1782 : }
1783 2 : const char *pszFormat = CPLGetXMLValue(
1784 : psFileAreaBrowse, "Encoded_Image.encoding_standard_id", "");
1785 3 : const char *const apszAllowedDrivers[] = {EQUAL(pszFormat, "TIFF") ? "GTiff"
1786 1 : : EQUAL(pszFormat, "PNG")
1787 1 : ? "PNG"
1788 : : nullptr,
1789 2 : nullptr};
1790 2 : if (apszAllowedDrivers[0] == nullptr)
1791 : {
1792 0 : CPLError(CE_Failure, CPLE_NotSupported,
1793 : "Unhandled encoding_standard_id=%s", pszFormat);
1794 0 : return nullptr;
1795 : }
1796 : const std::string osImageFullFilename = CPLFormFilenameSafe(
1797 4 : CPLGetPathSafe(poOpenInfo->pszFilename).c_str(), pszFilename, nullptr);
1798 : auto poExternalDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
1799 : osImageFullFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
1800 4 : apszAllowedDrivers));
1801 2 : if (!poExternalDS)
1802 0 : return nullptr;
1803 :
1804 4 : auto poDS = std::make_unique<PDS4Dataset>();
1805 2 : poDS->SetDescription(poOpenInfo->pszFilename);
1806 2 : poDS->m_poExternalDS = poExternalDS.release();
1807 2 : poDS->nRasterXSize = poDS->m_poExternalDS->GetRasterXSize();
1808 2 : poDS->nRasterYSize = poDS->m_poExternalDS->GetRasterYSize();
1809 2 : poDS->eAccess = GA_ReadOnly;
1810 2 : poDS->m_osImageFilename = osImageFullFilename;
1811 :
1812 4 : for (int i = 0; i < poDS->m_poExternalDS->GetRasterCount(); i++)
1813 : {
1814 : auto poBand = std::make_unique<PDS4BrowseImageProxyRasterBand>(
1815 2 : poDS->m_poExternalDS->GetRasterBand(i + 1));
1816 2 : poDS->SetBand(i + 1, std::move(poBand));
1817 : }
1818 :
1819 2 : if (poDS->m_poExternalDS->GetGeoTransform(poDS->m_gt) == CE_None)
1820 : {
1821 1 : poDS->m_bGotTransform = true;
1822 : }
1823 2 : const auto poSRS = poDS->m_poExternalDS->GetSpatialRef();
1824 2 : if (poSRS)
1825 1 : poDS->m_oSRS = *poSRS;
1826 :
1827 6 : for (const char *pszDomain : {GDAL_MDD_DEFAULT, GDAL_MDD_IMAGE_STRUCTURE})
1828 : {
1829 8 : poDS->GDALDataset::SetMetadata(
1830 4 : poDS->m_poExternalDS->GetMetadata(pszDomain), pszDomain);
1831 : }
1832 :
1833 : // Expose XML content in xml:PDS4 metadata domain
1834 2 : GByte *pabyRet = nullptr;
1835 2 : CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, poOpenInfo->pszFilename, &pabyRet,
1836 : nullptr, 10 * 1024 * 1024));
1837 2 : if (pabyRet)
1838 : {
1839 2 : char *apszXML[2] = {reinterpret_cast<char *>(pabyRet), nullptr};
1840 2 : poDS->GDALDataset::SetMetadata(apszXML, "xml:PDS4");
1841 : }
1842 2 : VSIFree(pabyRet);
1843 :
1844 : /*--------------------------------------------------------------------------*/
1845 : /* Initialize any PAM information */
1846 : /*--------------------------------------------------------------------------*/
1847 2 : poDS->SetDescription(poOpenInfo->pszFilename);
1848 2 : poDS->TryLoadXML();
1849 :
1850 2 : return poDS;
1851 : }
1852 :
1853 : /************************************************************************/
1854 : /* Open() */
1855 : /************************************************************************/
1856 :
1857 : // See https://pds.nasa.gov/pds4/pds/v1/PDS4_PDS_1800.xsd
1858 : // and https://pds.nasa.gov/pds4/pds/v1/PDS4_PDS_1800.sch
1859 308 : std::unique_ptr<PDS4Dataset> PDS4Dataset::OpenInternal(GDALOpenInfo *poOpenInfo)
1860 : {
1861 308 : if (!PDS4DriverIdentify(poOpenInfo))
1862 0 : return nullptr;
1863 :
1864 616 : CPLString osXMLFilename(poOpenInfo->pszFilename);
1865 308 : int nFAOIdxLookup = -1;
1866 308 : int nArrayIdxLookup = -1;
1867 308 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "PDS4:"))
1868 : {
1869 : char **papszTokens =
1870 15 : CSLTokenizeString2(poOpenInfo->pszFilename, ":", 0);
1871 15 : int nCount = CSLCount(papszTokens);
1872 15 : if (nCount == 5 && strlen(papszTokens[1]) == 1 &&
1873 1 : (papszTokens[2][0] == '\\' || papszTokens[2][0] == '/'))
1874 : {
1875 1 : osXMLFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
1876 1 : nFAOIdxLookup = atoi(papszTokens[3]);
1877 1 : nArrayIdxLookup = atoi(papszTokens[4]);
1878 : }
1879 14 : else if (nCount == 5 && (EQUAL(papszTokens[1], "/vsicurl/http") ||
1880 0 : EQUAL(papszTokens[1], "/vsicurl/https")))
1881 : {
1882 0 : osXMLFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
1883 0 : nFAOIdxLookup = atoi(papszTokens[3]);
1884 0 : nArrayIdxLookup = atoi(papszTokens[4]);
1885 : }
1886 14 : else if (nCount == 4)
1887 : {
1888 13 : osXMLFilename = papszTokens[1];
1889 13 : nFAOIdxLookup = atoi(papszTokens[2]);
1890 13 : nArrayIdxLookup = atoi(papszTokens[3]);
1891 : }
1892 : else
1893 : {
1894 1 : CPLError(CE_Failure, CPLE_AppDefined,
1895 : "Invalid syntax for PDS4 subdataset name");
1896 1 : CSLDestroy(papszTokens);
1897 1 : return nullptr;
1898 : }
1899 14 : CSLDestroy(papszTokens);
1900 : }
1901 :
1902 614 : CPLXMLTreeCloser oCloser(CPLParseXMLFile(osXMLFilename));
1903 307 : CPLXMLNode *psRoot = oCloser.get();
1904 307 : CPLStripXMLNamespace(psRoot, nullptr, TRUE);
1905 :
1906 614 : GDALAccess eAccess = STARTS_WITH_CI(poOpenInfo->pszFilename, "PDS4:")
1907 307 : ? GA_ReadOnly
1908 : : poOpenInfo->eAccess;
1909 :
1910 307 : CPLXMLNode *psProduct = CPLGetXMLNode(psRoot, "=Product_Observational");
1911 307 : if (psProduct == nullptr)
1912 : {
1913 6 : eAccess = GA_ReadOnly;
1914 6 : psProduct = CPLGetXMLNode(psRoot, "=Product_Ancillary");
1915 6 : if (psProduct == nullptr)
1916 : {
1917 6 : psProduct = CPLGetXMLNode(psRoot, "=Product_Collection");
1918 : }
1919 6 : if (psProduct == nullptr)
1920 : {
1921 5 : psProduct = CPLGetXMLNode(psRoot, "=Product_Browse");
1922 5 : if (psProduct)
1923 2 : return OpenBrowse(poOpenInfo, psProduct);
1924 : }
1925 : }
1926 305 : if (psProduct == nullptr)
1927 : {
1928 3 : return nullptr;
1929 : }
1930 :
1931 : // Test case:
1932 : // https://starbase.jpl.nasa.gov/pds4/1700/dph_example_products/test_Images_DisplaySettings/TestPattern_Image/TestPattern.xml
1933 302 : const char *pszVertDir = CPLGetXMLValue(
1934 : psProduct,
1935 : "Observation_Area.Discipline_Area.Display_Settings.Display_Direction."
1936 : "vertical_display_direction",
1937 : "");
1938 302 : const bool bBottomToTop = EQUAL(pszVertDir, "Bottom to Top");
1939 :
1940 302 : const char *pszHorizDir = CPLGetXMLValue(
1941 : psProduct,
1942 : "Observation_Area.Discipline_Area.Display_Settings.Display_Direction."
1943 : "horizontal_display_direction",
1944 : "");
1945 302 : const bool bRightToLeft = EQUAL(pszHorizDir, "Right to Left");
1946 :
1947 604 : auto poDS = std::make_unique<PDS4Dataset>();
1948 302 : poDS->m_osXMLFilename = osXMLFilename;
1949 302 : poDS->eAccess = eAccess;
1950 302 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
1951 :
1952 604 : CPLStringList aosSubdatasets;
1953 302 : int nFAOIdx = 0;
1954 2946 : for (CPLXMLNode *psIter = psProduct->psChild; psIter != nullptr;
1955 2644 : psIter = psIter->psNext)
1956 : {
1957 2646 : if (psIter->eType != CXT_Element ||
1958 933 : (strcmp(psIter->pszValue, "File_Area_Observational") != 0 &&
1959 602 : strcmp(psIter->pszValue, "File_Area_Ancillary") != 0 &&
1960 602 : strcmp(psIter->pszValue, "File_Area_Inventory") != 0))
1961 : {
1962 2314 : continue;
1963 : }
1964 :
1965 332 : nFAOIdx++;
1966 332 : CPLXMLNode *psFile = CPLGetXMLNode(psIter, "File");
1967 332 : if (psFile == nullptr)
1968 : {
1969 1 : continue;
1970 : }
1971 331 : const char *pszFilename = CPLGetXMLValue(psFile, "file_name", nullptr);
1972 331 : if (pszFilename == nullptr)
1973 : {
1974 1 : continue;
1975 : }
1976 :
1977 693 : for (CPLXMLNode *psSubIter = psFile->psChild; psSubIter != nullptr;
1978 363 : psSubIter = psSubIter->psNext)
1979 : {
1980 363 : if (psSubIter->eType == CXT_Comment &&
1981 14 : EQUAL(psSubIter->pszValue, PREEXISTING_BINARY_FILE))
1982 : {
1983 14 : poDS->m_bCreatedFromExistingBinaryFile = true;
1984 : }
1985 : }
1986 :
1987 330 : int nArrayIdx = 0;
1988 330 : for (CPLXMLNode *psSubIter = psIter->psChild;
1989 1050 : (nFAOIdxLookup < 0 || nFAOIdxLookup == nFAOIdx) &&
1990 : psSubIter != nullptr;
1991 720 : psSubIter = psSubIter->psNext)
1992 : {
1993 722 : if (psSubIter->eType != CXT_Element)
1994 : {
1995 504 : continue;
1996 : }
1997 722 : int nDIM = 0;
1998 722 : if (STARTS_WITH(psSubIter->pszValue, "Array_1D"))
1999 : {
2000 0 : nDIM = 1;
2001 : }
2002 722 : else if (STARTS_WITH(psSubIter->pszValue, "Array_2D"))
2003 : {
2004 6 : nDIM = 2;
2005 : }
2006 716 : else if (STARTS_WITH(psSubIter->pszValue, "Array_3D"))
2007 : {
2008 241 : nDIM = 3;
2009 : }
2010 475 : else if (strcmp(psSubIter->pszValue, "Array") == 0)
2011 : {
2012 3 : nDIM = atoi(CPLGetXMLValue(psSubIter, "axes", "0"));
2013 : }
2014 472 : else if (strcmp(psSubIter->pszValue, "Table_Character") == 0)
2015 : {
2016 22 : poDS->OpenTableCharacter(pszFilename, psSubIter);
2017 22 : continue;
2018 : }
2019 450 : else if (strcmp(psSubIter->pszValue, "Table_Binary") == 0)
2020 : {
2021 11 : poDS->OpenTableBinary(pszFilename, psSubIter);
2022 11 : continue;
2023 : }
2024 439 : else if (strcmp(psSubIter->pszValue, "Table_Delimited") == 0 ||
2025 376 : strcmp(psSubIter->pszValue, "Inventory") == 0)
2026 : {
2027 64 : poDS->OpenTableDelimited(pszFilename, psSubIter);
2028 64 : continue;
2029 : }
2030 625 : if (nDIM == 0)
2031 : {
2032 375 : continue;
2033 : }
2034 250 : if (!(nDIM >= 1 && nDIM <= 3))
2035 : {
2036 0 : CPLError(CE_Warning, CPLE_NotSupported,
2037 : "Array with %d dimensions not supported", nDIM);
2038 0 : continue;
2039 : }
2040 :
2041 250 : nArrayIdx++;
2042 : // Does it match a selected subdataset ?
2043 250 : if (nArrayIdxLookup > 0 && nArrayIdx != nArrayIdxLookup)
2044 : {
2045 13 : continue;
2046 : }
2047 :
2048 : const char *pszArrayName =
2049 237 : CPLGetXMLValue(psSubIter, "name", nullptr);
2050 : const char *pszArrayId =
2051 237 : CPLGetXMLValue(psSubIter, "local_identifier", nullptr);
2052 : vsi_l_offset nOffset = static_cast<vsi_l_offset>(
2053 237 : CPLAtoGIntBig(CPLGetXMLValue(psSubIter, "offset", "0")));
2054 :
2055 : const char *pszAxisIndexOrder =
2056 237 : CPLGetXMLValue(psSubIter, "axis_index_order", "");
2057 237 : if (!EQUAL(pszAxisIndexOrder, "Last Index Fastest"))
2058 : {
2059 1 : CPLError(CE_Warning, CPLE_NotSupported,
2060 : "axis_index_order = '%s' unhandled",
2061 : pszAxisIndexOrder);
2062 1 : continue;
2063 : }
2064 :
2065 : // Figure out data type
2066 : const char *pszDataType =
2067 236 : CPLGetXMLValue(psSubIter, "Element_Array.data_type", "");
2068 236 : GDALDataType eDT = GDT_UInt8;
2069 236 : bool bLSBOrder = strstr(pszDataType, "LSB") != nullptr;
2070 :
2071 : // ComplexLSB16', 'ComplexLSB8', 'ComplexMSB16', 'ComplexMSB8',
2072 : // 'IEEE754LSBDouble', 'IEEE754LSBSingle', 'IEEE754MSBDouble',
2073 : // 'IEEE754MSBSingle', 'SignedBitString', 'SignedByte',
2074 : // 'SignedLSB2', 'SignedLSB4', 'SignedLSB8', 'SignedMSB2',
2075 : // 'SignedMSB4', 'SignedMSB8', 'UnsignedBitString', 'UnsignedByte',
2076 : // 'UnsignedLSB2', 'UnsignedLSB4', 'UnsignedLSB8', 'UnsignedMSB2',
2077 : // 'UnsignedMSB4', 'UnsignedMSB8'
2078 236 : if (EQUAL(pszDataType, "ComplexLSB16") ||
2079 232 : EQUAL(pszDataType, "ComplexMSB16"))
2080 : {
2081 6 : eDT = GDT_CFloat64;
2082 : }
2083 230 : else if (EQUAL(pszDataType, "ComplexLSB8") ||
2084 226 : EQUAL(pszDataType, "ComplexMSB8"))
2085 : {
2086 4 : eDT = GDT_CFloat32;
2087 : }
2088 226 : else if (EQUAL(pszDataType, "IEEE754LSBDouble") ||
2089 219 : EQUAL(pszDataType, "IEEE754MSBDouble"))
2090 : {
2091 7 : eDT = GDT_Float64;
2092 : }
2093 219 : else if (EQUAL(pszDataType, "IEEE754LSBSingle") ||
2094 208 : EQUAL(pszDataType, "IEEE754MSBSingle"))
2095 : {
2096 12 : eDT = GDT_Float32;
2097 : }
2098 : // SignedBitString unhandled
2099 207 : else if (EQUAL(pszDataType, "SignedByte"))
2100 : {
2101 6 : eDT = GDT_Int8;
2102 : }
2103 201 : else if (EQUAL(pszDataType, "SignedLSB2") ||
2104 197 : EQUAL(pszDataType, "SignedMSB2"))
2105 : {
2106 6 : eDT = GDT_Int16;
2107 : }
2108 195 : else if (EQUAL(pszDataType, "SignedLSB4") ||
2109 191 : EQUAL(pszDataType, "SignedMSB4"))
2110 : {
2111 4 : eDT = GDT_Int32;
2112 : }
2113 191 : else if (EQUAL(pszDataType, "SignedLSB8") ||
2114 187 : EQUAL(pszDataType, "SignedMSB8"))
2115 : {
2116 4 : eDT = GDT_Int64;
2117 : }
2118 187 : else if (EQUAL(pszDataType, "UnsignedByte"))
2119 : {
2120 170 : eDT = GDT_UInt8;
2121 : }
2122 17 : else if (EQUAL(pszDataType, "UnsignedLSB2") ||
2123 9 : EQUAL(pszDataType, "UnsignedMSB2"))
2124 : {
2125 8 : eDT = GDT_UInt16;
2126 : }
2127 9 : else if (EQUAL(pszDataType, "UnsignedLSB4") ||
2128 5 : EQUAL(pszDataType, "UnsignedMSB4"))
2129 : {
2130 4 : eDT = GDT_UInt32;
2131 : }
2132 5 : else if (EQUAL(pszDataType, "UnsignedLSB8") ||
2133 1 : EQUAL(pszDataType, "UnsignedMSB8"))
2134 : {
2135 4 : eDT = GDT_UInt64;
2136 : }
2137 : else
2138 : {
2139 1 : CPLDebug("PDS4", "data_type = '%s' unhandled", pszDataType);
2140 1 : continue;
2141 : }
2142 :
2143 235 : poDS->m_osUnits =
2144 470 : CPLGetXMLValue(psSubIter, "Element_Array.unit", "");
2145 :
2146 235 : double dfValueOffset = CPLAtof(
2147 : CPLGetXMLValue(psSubIter, "Element_Array.value_offset", "0"));
2148 235 : double dfValueScale = CPLAtof(
2149 : CPLGetXMLValue(psSubIter, "Element_Array.scaling_factor", "1"));
2150 :
2151 : // Parse Axis_Array elements
2152 235 : int l_nBands = 1;
2153 235 : int nLines = 0;
2154 235 : int nSamples = 0;
2155 235 : std::vector<int> anElements;
2156 235 : std::vector<std::string> axisNames;
2157 235 : std::vector<char> dimSemantics;
2158 235 : anElements.resize(nDIM);
2159 235 : axisNames.resize(nDIM);
2160 235 : dimSemantics.resize(nDIM);
2161 235 : int iBandIdx = -1;
2162 235 : int iLineIdx = -1;
2163 235 : int iSampleIdx = -1;
2164 235 : int nAxisOKCount = 0;
2165 235 : for (CPLXMLNode *psAxisIter = psSubIter->psChild;
2166 2161 : psAxisIter != nullptr; psAxisIter = psAxisIter->psNext)
2167 : {
2168 1927 : if (psAxisIter->eType != CXT_Element ||
2169 1927 : strcmp(psAxisIter->pszValue, "Axis_Array") != 0)
2170 : {
2171 1224 : continue;
2172 : }
2173 : const char *pszAxisName =
2174 703 : CPLGetXMLValue(psAxisIter, "axis_name", nullptr);
2175 : const char *pszElements =
2176 703 : CPLGetXMLValue(psAxisIter, "elements", nullptr);
2177 : const char *pszSequenceNumber =
2178 703 : CPLGetXMLValue(psAxisIter, "sequence_number", nullptr);
2179 703 : if (pszAxisName == nullptr || pszElements == nullptr ||
2180 : pszSequenceNumber == nullptr)
2181 : {
2182 1 : continue;
2183 : }
2184 702 : int nSeqNumber = atoi(pszSequenceNumber);
2185 702 : if (nSeqNumber < 1 || nSeqNumber > nDIM)
2186 : {
2187 2 : CPLError(CE_Warning, CPLE_AppDefined,
2188 : "Invalid sequence_number = %s", pszSequenceNumber);
2189 2 : continue;
2190 : }
2191 700 : int nElements = atoi(pszElements);
2192 700 : if (nElements <= 0)
2193 : {
2194 1 : CPLError(CE_Warning, CPLE_AppDefined,
2195 : "Invalid elements = %s", pszElements);
2196 1 : continue;
2197 : }
2198 :
2199 699 : const int nIdx = nSeqNumber - 1;
2200 699 : if (STARTS_WITH_CI(pszAxisName, "Line"))
2201 : {
2202 234 : if (iLineIdx < 0)
2203 234 : iLineIdx = nIdx;
2204 : else
2205 : {
2206 0 : CPLError(CE_Warning, CPLE_AppDefined,
2207 : "Several axis with Line identifier");
2208 0 : break;
2209 : }
2210 : }
2211 465 : else if (STARTS_WITH_CI(pszAxisName, "Sample"))
2212 : {
2213 234 : if (iSampleIdx < 0)
2214 234 : iSampleIdx = nIdx;
2215 : else
2216 : {
2217 0 : CPLError(CE_Warning, CPLE_AppDefined,
2218 : "Several axis with Sample identifier");
2219 0 : break;
2220 : }
2221 : }
2222 231 : else if (STARTS_WITH_CI(pszAxisName, "Band"))
2223 : {
2224 229 : if (iBandIdx < 0)
2225 228 : iBandIdx = nIdx;
2226 : else
2227 : {
2228 1 : CPLError(CE_Warning, CPLE_AppDefined,
2229 : "Several axis with Band identifier");
2230 1 : break;
2231 : }
2232 : }
2233 :
2234 698 : anElements[nIdx] = nElements;
2235 698 : axisNames[nIdx] = pszAxisName;
2236 :
2237 698 : ++nAxisOKCount;
2238 : }
2239 :
2240 235 : if (nAxisOKCount != nDIM)
2241 : {
2242 1 : CPLError(CE_Warning, CPLE_AppDefined,
2243 : "Found only %d Axis_Array elements. %d expected",
2244 : nAxisOKCount, nDIM);
2245 1 : continue;
2246 : }
2247 :
2248 234 : if (nDIM == 1)
2249 : {
2250 0 : dimSemantics[0] = 'S';
2251 0 : nLines = 1;
2252 0 : nSamples = anElements[0];
2253 : }
2254 234 : else if (nDIM == 2)
2255 : {
2256 6 : if (iLineIdx < 0 || iSampleIdx < 0)
2257 : {
2258 0 : CPLDebug("PDS4", "Assume that axis %s is Line",
2259 0 : axisNames[0].c_str());
2260 0 : CPLDebug("PDS4", "Assume that axis %s is Sample",
2261 0 : axisNames[1].c_str());
2262 0 : iLineIdx = 0;
2263 0 : iSampleIdx = 1;
2264 : }
2265 6 : CPLAssert(iLineIdx >= 0 && iLineIdx < 2);
2266 6 : CPLAssert(iSampleIdx >= 0 && iSampleIdx < 2);
2267 6 : dimSemantics[iLineIdx] = 'L';
2268 6 : dimSemantics[iSampleIdx] = 'S';
2269 6 : nLines = anElements[iLineIdx];
2270 6 : nSamples = anElements[iSampleIdx];
2271 : }
2272 : else /* if (nDim == 3) */
2273 : {
2274 228 : if (iLineIdx < 0 || iSampleIdx < 0)
2275 : {
2276 0 : CPLDebug("PDS4", "Assume that axis %s is Band",
2277 0 : axisNames[0].c_str());
2278 0 : CPLDebug("PDS4", "Assume that axis %s is Line",
2279 0 : axisNames[1].c_str());
2280 0 : CPLDebug("PDS4", "Assume that axis %s is Sample",
2281 0 : axisNames[2].c_str());
2282 0 : iBandIdx = 0;
2283 0 : iLineIdx = 1;
2284 0 : iSampleIdx = 2;
2285 : }
2286 228 : else if (iBandIdx < 0)
2287 : {
2288 1 : CPLAssert(iLineIdx >= 0 && iLineIdx < 3);
2289 1 : CPLAssert(iSampleIdx >= 0 && iSampleIdx < 3);
2290 1 : bool abUsedIndices[3] = {false, false, false};
2291 1 : abUsedIndices[iLineIdx] = true;
2292 1 : abUsedIndices[iSampleIdx] = true;
2293 4 : for (int i = 0; i < 3; ++i)
2294 : {
2295 3 : if (!abUsedIndices[i])
2296 1 : iBandIdx = i;
2297 : }
2298 : }
2299 228 : CPLAssert(iLineIdx >= 0 && iLineIdx < 3);
2300 228 : CPLAssert(iSampleIdx >= 0 && iSampleIdx < 3);
2301 228 : CPLAssert(iBandIdx >= 0 && iSampleIdx < 3);
2302 228 : dimSemantics[iBandIdx] = 'B';
2303 228 : dimSemantics[iLineIdx] = 'L';
2304 228 : dimSemantics[iSampleIdx] = 'S';
2305 228 : l_nBands = anElements[iBandIdx];
2306 228 : nLines = anElements[iLineIdx];
2307 228 : nSamples = anElements[iSampleIdx];
2308 : }
2309 :
2310 468 : if (!GDALCheckDatasetDimensions(nSamples, nLines) ||
2311 234 : !GDALCheckBandCount(l_nBands, FALSE))
2312 : {
2313 1 : continue;
2314 : }
2315 :
2316 : // Compute pixel, line and band spacing
2317 233 : vsi_l_offset nSpacing = GDALGetDataTypeSizeBytes(eDT);
2318 233 : int nPixelOffset = 0;
2319 233 : int nLineOffset = 0;
2320 233 : vsi_l_offset nBandOffset = 0;
2321 233 : int nCountPreviousDim = 1;
2322 924 : for (int i = nDIM - 1; i >= 0; i--)
2323 : {
2324 693 : if (dimSemantics[i] == 'S')
2325 : {
2326 233 : if (nCountPreviousDim > 0 &&
2327 233 : nSpacing > static_cast<vsi_l_offset>(INT_MAX /
2328 : nCountPreviousDim))
2329 : {
2330 1 : CPLError(CE_Failure, CPLE_NotSupported,
2331 : "Integer overflow");
2332 1 : return nullptr;
2333 : }
2334 232 : nPixelOffset =
2335 232 : static_cast<int>(nSpacing * nCountPreviousDim);
2336 232 : nSpacing = nPixelOffset;
2337 : }
2338 460 : else if (dimSemantics[i] == 'L')
2339 : {
2340 233 : if (nCountPreviousDim > 0 &&
2341 233 : nSpacing > static_cast<vsi_l_offset>(INT_MAX /
2342 : nCountPreviousDim))
2343 : {
2344 1 : CPLError(CE_Failure, CPLE_NotSupported,
2345 : "Integer overflow");
2346 1 : return nullptr;
2347 : }
2348 232 : nLineOffset =
2349 232 : static_cast<int>(nSpacing * nCountPreviousDim);
2350 232 : nSpacing = nLineOffset;
2351 : }
2352 : else
2353 : {
2354 227 : nBandOffset = nSpacing * nCountPreviousDim;
2355 227 : nSpacing = nBandOffset;
2356 : }
2357 691 : nCountPreviousDim = anElements[i];
2358 : }
2359 :
2360 : // Retrieve no data value
2361 231 : bool bNoDataSet = false;
2362 231 : double dfNoData = 0.0;
2363 231 : bool bNoDataSetInt64 = false;
2364 231 : int64_t nNoDataInt64 = 0;
2365 231 : bool bNoDataSetUInt64 = false;
2366 231 : int64_t nNoDataUInt64 = 0;
2367 231 : std::vector<double> adfConstants;
2368 231 : CPLXMLNode *psSC = CPLGetXMLNode(psSubIter, "Special_Constants");
2369 231 : if (psSC)
2370 : {
2371 : const auto GetNoDataFromConstant =
2372 76 : [psSC, eDT, &bNoDataSetInt64, &nNoDataInt64,
2373 : &bNoDataSetUInt64, &nNoDataUInt64, &bNoDataSet,
2374 372 : &dfNoData](const char *pszConstantName)
2375 : {
2376 76 : if (const char *pszVal =
2377 76 : CPLGetXMLValue(psSC, pszConstantName, nullptr))
2378 : {
2379 75 : if (eDT == GDT_Int64)
2380 : {
2381 2 : bNoDataSetInt64 = true;
2382 2 : nNoDataInt64 = std::strtoll(pszVal, nullptr, 10);
2383 : }
2384 73 : else if (eDT == GDT_UInt64)
2385 : {
2386 2 : bNoDataSetUInt64 = true;
2387 2 : nNoDataUInt64 = std::strtoull(pszVal, nullptr, 10);
2388 : }
2389 : else
2390 : {
2391 : auto val =
2392 71 : ConstantToDouble(pszConstantName, pszVal);
2393 71 : if (val)
2394 : {
2395 71 : bNoDataSet = true;
2396 71 : dfNoData = *val;
2397 : }
2398 : }
2399 75 : return true;
2400 : }
2401 1 : return false;
2402 75 : };
2403 :
2404 75 : if (!GetNoDataFromConstant("missing_constant"))
2405 : {
2406 : // For example in https://d34uoeqxvp7znu.cloudfront.net/data/l2/200908/20090811/m3g20090811t012112_l2.xml
2407 1 : GetNoDataFromConstant("invalid_constant");
2408 : }
2409 :
2410 75 : const char *const apszConstantNames[] = {
2411 : "saturated_constant",
2412 : "missing_constant",
2413 : "error_constant",
2414 : "invalid_constant",
2415 : "unknown_constant",
2416 : "not_applicable_constant",
2417 : "high_instrument_saturation",
2418 : "high_representation_saturation",
2419 : "low_instrument_saturation",
2420 : "low_representation_saturation"};
2421 825 : for (const char *pszItem : apszConstantNames)
2422 : {
2423 750 : if (const char *pszConstant =
2424 750 : CPLGetXMLValue(psSC, pszItem, nullptr))
2425 : {
2426 109 : auto val = ConstantToDouble(pszItem, pszConstant);
2427 109 : if (val)
2428 : {
2429 109 : adfConstants.push_back(*val);
2430 : }
2431 : }
2432 : }
2433 : }
2434 :
2435 : // Add subdatasets
2436 231 : const int nSDSIdx = 1 + aosSubdatasets.size() / 2;
2437 : aosSubdatasets.SetNameValue(
2438 : CPLSPrintf("SUBDATASET_%d_NAME", nSDSIdx),
2439 : CPLSPrintf("PDS4:%s:%d:%d", osXMLFilename.c_str(), nFAOIdx,
2440 231 : nArrayIdx));
2441 : aosSubdatasets.SetNameValue(
2442 : CPLSPrintf("SUBDATASET_%d_DESC", nSDSIdx),
2443 : CPLSPrintf("Image file %s, array %s", pszFilename,
2444 : pszArrayName ? pszArrayName
2445 231 : : pszArrayId ? pszArrayId
2446 462 : : CPLSPrintf("%d", nArrayIdx)));
2447 :
2448 231 : if (poDS->nBands != 0)
2449 14 : continue;
2450 :
2451 : const std::string osImageFullFilename = CPLFormFilenameSafe(
2452 217 : CPLGetPathSafe(osXMLFilename.c_str()).c_str(), pszFilename,
2453 217 : nullptr);
2454 217 : if (CPLHasPathTraversal(pszFilename))
2455 : {
2456 0 : CPLError(CE_Failure, CPLE_NotSupported,
2457 : "Path traversal detected in %s", pszFilename);
2458 0 : return nullptr;
2459 : }
2460 217 : VSILFILE *fp = VSIFOpenExL(
2461 : osImageFullFilename.c_str(),
2462 217 : (poOpenInfo->eAccess == GA_Update) ? "rb+" : "rb", true);
2463 217 : if (fp == nullptr)
2464 : {
2465 1 : CPLError(CE_Warning, CPLE_FileIO, "Cannot open %s: %s",
2466 : osImageFullFilename.c_str(), VSIGetLastErrorMsg());
2467 1 : continue;
2468 : }
2469 216 : poDS->nRasterXSize = nSamples;
2470 216 : poDS->nRasterYSize = nLines;
2471 216 : poDS->m_osImageFilename = osImageFullFilename;
2472 216 : poDS->m_fpImage = fp;
2473 216 : poDS->m_bIsLSB = bLSBOrder;
2474 :
2475 35 : if (l_nBands > 1 && dimSemantics[0] == 'B' &&
2476 251 : dimSemantics[1] == 'L' && dimSemantics[2] == 'S')
2477 : {
2478 27 : poDS->GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "BAND",
2479 : GDAL_MDD_IMAGE_STRUCTURE);
2480 : }
2481 35 : if (l_nBands > 1 && dimSemantics[0] == 'L' &&
2482 251 : dimSemantics[1] == 'S' && dimSemantics[2] == 'B')
2483 : {
2484 6 : poDS->GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
2485 : GDAL_MDD_IMAGE_STRUCTURE);
2486 : }
2487 :
2488 216 : CPLXMLNode *psOS = CPLGetXMLNode(psSubIter, "Object_Statistics");
2489 216 : const char *pszMin = nullptr;
2490 216 : const char *pszMax = nullptr;
2491 216 : const char *pszMean = nullptr;
2492 216 : const char *pszStdDev = nullptr;
2493 216 : if (psOS)
2494 : {
2495 17 : pszMin = CPLGetXMLValue(psOS, "minimum", nullptr);
2496 17 : pszMax = CPLGetXMLValue(psOS, "maximum", nullptr);
2497 17 : pszMean = CPLGetXMLValue(psOS, "mean", nullptr);
2498 17 : pszStdDev = CPLGetXMLValue(psOS, "standard_deviation", nullptr);
2499 : }
2500 :
2501 501 : for (int i = 0; i < l_nBands; i++)
2502 : {
2503 285 : vsi_l_offset nThisBandOffset = nOffset + nBandOffset * i;
2504 285 : if (bBottomToTop)
2505 : {
2506 2 : nThisBandOffset +=
2507 2 : static_cast<vsi_l_offset>(nLines - 1) * nLineOffset;
2508 : }
2509 285 : if (bRightToLeft)
2510 : {
2511 1 : nThisBandOffset +=
2512 1 : static_cast<vsi_l_offset>(nSamples - 1) * nPixelOffset;
2513 : }
2514 : auto poBand = std::make_unique<PDS4RawRasterBand>(
2515 285 : poDS.get(), i + 1, poDS->m_fpImage, nThisBandOffset,
2516 285 : bRightToLeft ? -nPixelOffset : nPixelOffset,
2517 285 : bBottomToTop ? -nLineOffset : nLineOffset, eDT,
2518 285 : bLSBOrder ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
2519 285 : : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN);
2520 285 : if (!poBand->IsValid())
2521 0 : return nullptr;
2522 285 : if (bNoDataSet)
2523 : {
2524 75 : poBand->SetNoDataValue(dfNoData);
2525 : }
2526 210 : else if (bNoDataSetInt64)
2527 : {
2528 2 : poBand->SetNoDataValueAsInt64(nNoDataInt64);
2529 : }
2530 208 : else if (bNoDataSetUInt64)
2531 : {
2532 2 : poBand->SetNoDataValueAsUInt64(nNoDataUInt64);
2533 : }
2534 285 : poBand->SetOffset(dfValueOffset);
2535 285 : poBand->SetScale(dfValueScale);
2536 :
2537 285 : if (l_nBands == 1)
2538 : {
2539 181 : if (pszMin)
2540 : {
2541 17 : poBand->GDALRasterBand::SetMetadataItem(
2542 : "STATISTICS_MINIMUM", pszMin);
2543 : }
2544 181 : if (pszMax)
2545 : {
2546 17 : poBand->GDALRasterBand::SetMetadataItem(
2547 : "STATISTICS_MAXIMUM", pszMax);
2548 : }
2549 181 : if (pszMean)
2550 : {
2551 17 : poBand->GDALRasterBand::SetMetadataItem(
2552 : "STATISTICS_MEAN", pszMean);
2553 : }
2554 181 : if (pszStdDev)
2555 : {
2556 17 : poBand->GDALRasterBand::SetMetadataItem(
2557 : "STATISTICS_STDDEV", pszStdDev);
2558 : }
2559 : }
2560 :
2561 : // Only instantiate explicit mask band if we have at least one
2562 : // special constant (that is not the missing_constant,
2563 : // already exposed as nodata value)
2564 558 : if (!GDALDataTypeIsComplex(eDT) &&
2565 538 : (CPLTestBool(CPLGetConfigOption("PDS4_FORCE_MASK", "NO")) ||
2566 499 : adfConstants.size() >= 2 ||
2567 282 : (adfConstants.size() == 1 && !bNoDataSet)))
2568 : {
2569 86 : poBand->SetMaskBand(std::make_unique<PDS4MaskBand>(
2570 86 : poBand.get(), adfConstants));
2571 : }
2572 :
2573 285 : poDS->SetBand(i + 1, std::move(poBand));
2574 : }
2575 : }
2576 : }
2577 :
2578 300 : if (nFAOIdxLookup < 0 && aosSubdatasets.size() > 2)
2579 : {
2580 10 : poDS->GDALDataset::SetMetadata(aosSubdatasets.List(),
2581 : GDAL_MDD_SUBDATASETS);
2582 : }
2583 290 : else if (poDS->nBands == 0 &&
2584 300 : (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
2585 10 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
2586 : {
2587 5 : return nullptr;
2588 : }
2589 285 : else if (poDS->m_apoLayers.empty() &&
2590 395 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
2591 110 : (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0)
2592 : {
2593 0 : return nullptr;
2594 : }
2595 :
2596 : // Expose XML content in xml:PDS4 metadata domain
2597 295 : GByte *pabyRet = nullptr;
2598 295 : CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osXMLFilename, &pabyRet, nullptr,
2599 : 10 * 1024 * 1024));
2600 295 : if (pabyRet)
2601 : {
2602 295 : char *apszXML[2] = {reinterpret_cast<char *>(pabyRet), nullptr};
2603 295 : poDS->GDALDataset::SetMetadata(apszXML, "xml:PDS4");
2604 : }
2605 295 : VSIFree(pabyRet);
2606 :
2607 : /*--------------------------------------------------------------------------*/
2608 : /* Parse georeferencing info */
2609 : /*--------------------------------------------------------------------------*/
2610 295 : poDS->ReadGeoreferencing(psProduct);
2611 :
2612 : /*--------------------------------------------------------------------------*/
2613 : /* Check for overviews */
2614 : /*--------------------------------------------------------------------------*/
2615 295 : poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
2616 :
2617 : /*--------------------------------------------------------------------------*/
2618 : /* Initialize any PAM information */
2619 : /*--------------------------------------------------------------------------*/
2620 295 : poDS->SetDescription(poOpenInfo->pszFilename);
2621 295 : poDS->TryLoadXML();
2622 :
2623 295 : return poDS;
2624 : }
2625 :
2626 : /************************************************************************/
2627 : /* IsCARTVersionGTE() */
2628 : /************************************************************************/
2629 :
2630 : // Returns true is pszCur >= pszRef
2631 : // Must be things like 1900, 1B00, 1D00_1933 ...
2632 489 : static bool IsCARTVersionGTE(const char *pszCur, const char *pszRef)
2633 : {
2634 489 : return strcmp(pszCur, pszRef) >= 0;
2635 : }
2636 :
2637 : /************************************************************************/
2638 : /* WriteGeoreferencing() */
2639 : /************************************************************************/
2640 :
2641 98 : void PDS4Dataset::WriteGeoreferencing(CPLXMLNode *psCart,
2642 : const char *pszCARTVersion)
2643 : {
2644 98 : bool bHasBoundingBox = false;
2645 98 : double adfX[4] = {0};
2646 98 : double adfY[4] = {0};
2647 98 : CPLString osPrefix;
2648 98 : const char *pszColon = strchr(psCart->pszValue, ':');
2649 98 : if (pszColon)
2650 98 : osPrefix.assign(psCart->pszValue, pszColon - psCart->pszValue + 1);
2651 :
2652 98 : if (m_bGotTransform)
2653 : {
2654 96 : bHasBoundingBox = true;
2655 :
2656 : // upper left
2657 96 : adfX[0] = m_gt.xorig;
2658 96 : adfY[0] = m_gt.yorig;
2659 :
2660 : // upper right
2661 96 : adfX[1] = m_gt.xorig + m_gt.xscale * nRasterXSize;
2662 96 : adfY[1] = m_gt.yorig;
2663 :
2664 : // lower left
2665 96 : adfX[2] = m_gt.xorig;
2666 96 : adfY[2] = m_gt.yorig + m_gt.yscale * nRasterYSize;
2667 :
2668 : // lower right
2669 96 : adfX[3] = m_gt.xorig + m_gt.xscale * nRasterXSize;
2670 96 : adfY[3] = m_gt.yorig + m_gt.yscale * nRasterYSize;
2671 : }
2672 : else
2673 : {
2674 2 : OGRLayer *poLayer = GetLayer(0);
2675 2 : OGREnvelope sEnvelope;
2676 2 : if (poLayer->GetExtent(&sEnvelope) == OGRERR_NONE)
2677 : {
2678 1 : bHasBoundingBox = true;
2679 :
2680 1 : adfX[0] = sEnvelope.MinX;
2681 1 : adfY[0] = sEnvelope.MaxY;
2682 :
2683 1 : adfX[1] = sEnvelope.MaxX;
2684 1 : adfY[1] = sEnvelope.MaxY;
2685 :
2686 1 : adfX[2] = sEnvelope.MinX;
2687 1 : adfY[2] = sEnvelope.MinY;
2688 :
2689 1 : adfX[3] = sEnvelope.MaxX;
2690 1 : adfY[3] = sEnvelope.MinY;
2691 : }
2692 : }
2693 :
2694 98 : if (bHasBoundingBox && !m_oSRS.IsGeographic())
2695 : {
2696 33 : bHasBoundingBox = false;
2697 33 : OGRSpatialReference *poSRSLongLat = m_oSRS.CloneGeogCS();
2698 33 : if (poSRSLongLat)
2699 : {
2700 33 : poSRSLongLat->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2701 : OGRCoordinateTransformation *poCT =
2702 33 : OGRCreateCoordinateTransformation(&m_oSRS, poSRSLongLat);
2703 33 : if (poCT)
2704 : {
2705 33 : if (poCT->Transform(4, adfX, adfY))
2706 : {
2707 33 : bHasBoundingBox = true;
2708 : }
2709 33 : delete poCT;
2710 : }
2711 33 : delete poSRSLongLat;
2712 : }
2713 : }
2714 :
2715 98 : if (!bHasBoundingBox)
2716 : {
2717 : // Write dummy values
2718 1 : adfX[0] = -180.0;
2719 1 : adfY[0] = 90.0;
2720 1 : adfX[1] = 180.0;
2721 1 : adfY[1] = 90.0;
2722 1 : adfX[2] = -180.0;
2723 1 : adfY[2] = -90.0;
2724 1 : adfX[3] = 180.0;
2725 1 : adfY[3] = -90.0;
2726 : }
2727 :
2728 196 : const char *pszLongitudeDirection = CSLFetchNameValueDef(
2729 98 : m_papszCreationOptions, "LONGITUDE_DIRECTION", "Positive East");
2730 98 : const double dfLongitudeMultiplier =
2731 98 : EQUAL(pszLongitudeDirection, "Positive West") ? -1 : 1;
2732 212 : const auto FixLong = [dfLongitudeMultiplier](double dfLon)
2733 212 : { return dfLon * dfLongitudeMultiplier; };
2734 :
2735 : // Note: starting with CART 1900, Spatial_Domain is actually optional
2736 98 : CPLXMLNode *psSD = CPLCreateXMLNode(psCart, CXT_Element,
2737 196 : (osPrefix + "Spatial_Domain").c_str());
2738 98 : CPLXMLNode *psBC = CPLCreateXMLNode(
2739 196 : psSD, CXT_Element, (osPrefix + "Bounding_Coordinates").c_str());
2740 :
2741 : const char *pszBoundingDegrees =
2742 98 : CSLFetchNameValue(m_papszCreationOptions, "BOUNDING_DEGREES");
2743 98 : double dfWest = FixLong(
2744 98 : std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3])));
2745 98 : double dfEast = FixLong(
2746 98 : std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3])));
2747 : double dfNorth =
2748 98 : std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
2749 : double dfSouth =
2750 98 : std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
2751 98 : if (pszBoundingDegrees)
2752 : {
2753 1 : char **papszTokens = CSLTokenizeString2(pszBoundingDegrees, ",", 0);
2754 1 : if (CSLCount(papszTokens) == 4)
2755 : {
2756 1 : dfWest = CPLAtof(papszTokens[0]);
2757 1 : dfSouth = CPLAtof(papszTokens[1]);
2758 1 : dfEast = CPLAtof(papszTokens[2]);
2759 1 : dfNorth = CPLAtof(papszTokens[3]);
2760 : }
2761 1 : CSLDestroy(papszTokens);
2762 : }
2763 :
2764 196 : CPLAddXMLAttributeAndValue(
2765 : CPLCreateXMLElementAndValue(
2766 196 : psBC, (osPrefix + "west_bounding_coordinate").c_str(),
2767 : CPLSPrintf("%.17g", dfWest)),
2768 : "unit", "deg");
2769 196 : CPLAddXMLAttributeAndValue(
2770 : CPLCreateXMLElementAndValue(
2771 196 : psBC, (osPrefix + "east_bounding_coordinate").c_str(),
2772 : CPLSPrintf("%.17g", dfEast)),
2773 : "unit", "deg");
2774 196 : CPLAddXMLAttributeAndValue(
2775 : CPLCreateXMLElementAndValue(
2776 196 : psBC, (osPrefix + "north_bounding_coordinate").c_str(),
2777 : CPLSPrintf("%.17g", dfNorth)),
2778 : "unit", "deg");
2779 196 : CPLAddXMLAttributeAndValue(
2780 : CPLCreateXMLElementAndValue(
2781 196 : psBC, (osPrefix + "south_bounding_coordinate").c_str(),
2782 : CPLSPrintf("%.17g", dfSouth)),
2783 : "unit", "deg");
2784 :
2785 : CPLXMLNode *psSRI =
2786 98 : CPLCreateXMLNode(psCart, CXT_Element,
2787 196 : (osPrefix + "Spatial_Reference_Information").c_str());
2788 98 : CPLXMLNode *psHCSD = CPLCreateXMLNode(
2789 : psSRI, CXT_Element,
2790 196 : (osPrefix + "Horizontal_Coordinate_System_Definition").c_str());
2791 :
2792 98 : double dfUnrotatedULX = m_gt.xorig;
2793 98 : double dfUnrotatedULY = m_gt.yorig;
2794 98 : double dfUnrotatedResX = m_gt.xscale;
2795 98 : double dfUnrotatedResY = m_gt.yscale;
2796 98 : double dfMapProjectionRotation = 0.0;
2797 98 : if (m_gt.xscale == 0.0 && m_gt.xrot > 0.0 && m_gt.yrot > 0.0 &&
2798 1 : m_gt.yscale == 0.0)
2799 : {
2800 1 : dfUnrotatedULX = m_gt.yorig;
2801 1 : dfUnrotatedULY = -m_gt.xorig;
2802 1 : dfUnrotatedResX = m_gt.yrot;
2803 1 : dfUnrotatedResY = -m_gt.xrot;
2804 1 : dfMapProjectionRotation = 90.0;
2805 : }
2806 :
2807 98 : if (GetRasterCount() || m_oSRS.IsProjected())
2808 : {
2809 97 : CPLXMLNode *psPlanar = CPLCreateXMLNode(psHCSD, CXT_Element,
2810 194 : (osPrefix + "Planar").c_str());
2811 97 : CPLXMLNode *psMP = CPLCreateXMLNode(
2812 194 : psPlanar, CXT_Element, (osPrefix + "Map_Projection").c_str());
2813 97 : const char *pszProjection = m_oSRS.GetAttrValue("PROJECTION");
2814 194 : CPLString pszPDS4ProjectionName = "";
2815 : typedef std::pair<const char *, double> ProjParam;
2816 194 : std::vector<ProjParam> aoProjParams;
2817 :
2818 : const bool bUse_CART_1933_Or_Later =
2819 97 : IsCARTVersionGTE(pszCARTVersion, "1D00_1933");
2820 :
2821 : const bool bUse_CART_1950_Or_Later =
2822 97 : IsCARTVersionGTE(pszCARTVersion, "1G00_1950");
2823 :
2824 : const bool bUse_CART_1970_Or_Later =
2825 97 : IsCARTVersionGTE(pszCARTVersion, "1O00_1970");
2826 :
2827 97 : if (pszProjection == nullptr)
2828 : {
2829 63 : pszPDS4ProjectionName = "Equirectangular";
2830 63 : if (bUse_CART_1933_Or_Later)
2831 : {
2832 61 : aoProjParams.push_back(
2833 61 : ProjParam("latitude_of_projection_origin", 0.0));
2834 61 : aoProjParams.push_back(ProjParam("standard_parallel_1", 0.0));
2835 61 : aoProjParams.push_back(
2836 122 : ProjParam("longitude_of_central_meridian", 0.0));
2837 : }
2838 : else
2839 : {
2840 2 : aoProjParams.push_back(ProjParam("standard_parallel_1", 0.0));
2841 2 : aoProjParams.push_back(
2842 2 : ProjParam("longitude_of_central_meridian", 0.0));
2843 2 : aoProjParams.push_back(
2844 4 : ProjParam("latitude_of_projection_origin", 0.0));
2845 : }
2846 : }
2847 :
2848 34 : else if (EQUAL(pszProjection, SRS_PT_EQUIRECTANGULAR))
2849 : {
2850 2 : pszPDS4ProjectionName = "Equirectangular";
2851 2 : if (bUse_CART_1933_Or_Later)
2852 : {
2853 2 : aoProjParams.push_back(ProjParam(
2854 0 : "latitude_of_projection_origin",
2855 2 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2856 2 : aoProjParams.push_back(ProjParam(
2857 0 : "standard_parallel_1",
2858 2 : m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 1.0)));
2859 2 : aoProjParams.push_back(
2860 0 : ProjParam("longitude_of_central_meridian",
2861 4 : FixLong(m_oSRS.GetNormProjParm(
2862 : SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2863 : }
2864 : else
2865 : {
2866 0 : aoProjParams.push_back(ProjParam(
2867 0 : "standard_parallel_1",
2868 0 : m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 1.0)));
2869 0 : aoProjParams.push_back(
2870 0 : ProjParam("longitude_of_central_meridian",
2871 0 : FixLong(m_oSRS.GetNormProjParm(
2872 : SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2873 0 : aoProjParams.push_back(ProjParam(
2874 0 : "latitude_of_projection_origin",
2875 0 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2876 : }
2877 : }
2878 :
2879 32 : else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP))
2880 : {
2881 1 : pszPDS4ProjectionName = "Lambert Conformal Conic";
2882 1 : if (bUse_CART_1933_Or_Later)
2883 : {
2884 1 : if (bUse_CART_1970_Or_Later)
2885 : {
2886 : // Note: in EPSG (and PROJ "metadata" part), there is
2887 : // no standard_parallel_1 parameter for LCC_1SP. But
2888 : // for historical reason we do map the latitude of origin
2889 : // to +lat_1 (in addition to +lat_0). So do the same here.
2890 1 : aoProjParams.push_back(
2891 0 : ProjParam("standard_parallel_1",
2892 2 : m_oSRS.GetNormProjParm(
2893 : SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2894 : }
2895 1 : aoProjParams.push_back(
2896 0 : ProjParam("longitude_of_central_meridian",
2897 1 : FixLong(m_oSRS.GetNormProjParm(
2898 : SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2899 1 : aoProjParams.push_back(ProjParam(
2900 0 : "latitude_of_projection_origin",
2901 1 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2902 1 : aoProjParams.push_back(ProjParam(
2903 0 : "scale_factor_at_projection_origin",
2904 2 : m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
2905 : }
2906 : else
2907 : {
2908 0 : aoProjParams.push_back(ProjParam(
2909 0 : "scale_factor_at_projection_origin",
2910 0 : m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
2911 0 : aoProjParams.push_back(
2912 0 : ProjParam("longitude_of_central_meridian",
2913 0 : FixLong(m_oSRS.GetNormProjParm(
2914 : SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2915 0 : aoProjParams.push_back(ProjParam(
2916 0 : "latitude_of_projection_origin",
2917 0 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2918 : }
2919 : }
2920 :
2921 31 : else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP))
2922 : {
2923 1 : pszPDS4ProjectionName = "Lambert Conformal Conic";
2924 1 : aoProjParams.push_back(ProjParam(
2925 0 : "standard_parallel_1",
2926 1 : m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0)));
2927 1 : aoProjParams.push_back(ProjParam(
2928 0 : "standard_parallel_2",
2929 1 : m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0)));
2930 1 : aoProjParams.push_back(ProjParam(
2931 0 : "longitude_of_central_meridian",
2932 1 : FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2933 1 : aoProjParams.push_back(ProjParam(
2934 0 : "latitude_of_projection_origin",
2935 2 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2936 : }
2937 :
2938 30 : else if (EQUAL(pszProjection,
2939 : SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER))
2940 : {
2941 1 : pszPDS4ProjectionName = "Oblique Mercator";
2942 : // Proj params defined later
2943 : }
2944 :
2945 29 : else if (EQUAL(pszProjection,
2946 : SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN))
2947 : {
2948 1 : pszPDS4ProjectionName = "Oblique Mercator";
2949 : // Proj params defined later
2950 : }
2951 :
2952 28 : else if (EQUAL(pszProjection, SRS_PT_POLAR_STEREOGRAPHIC))
2953 : {
2954 1 : pszPDS4ProjectionName = "Polar Stereographic";
2955 1 : if (bUse_CART_1950_Or_Later)
2956 : {
2957 1 : aoProjParams.push_back(
2958 0 : ProjParam("longitude_of_central_meridian",
2959 1 : FixLong(m_oSRS.GetNormProjParm(
2960 : SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2961 1 : aoProjParams.push_back(ProjParam(
2962 0 : "latitude_of_projection_origin",
2963 1 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2964 1 : aoProjParams.push_back(ProjParam(
2965 0 : "scale_factor_at_projection_origin",
2966 2 : m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
2967 : }
2968 : else
2969 : {
2970 0 : aoProjParams.push_back(
2971 0 : ProjParam(bUse_CART_1933_Or_Later
2972 0 : ? "longitude_of_central_meridian"
2973 : : "straight_vertical_longitude_from_pole",
2974 0 : FixLong(m_oSRS.GetNormProjParm(
2975 : SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2976 0 : aoProjParams.push_back(ProjParam(
2977 0 : "scale_factor_at_projection_origin",
2978 0 : m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
2979 0 : aoProjParams.push_back(ProjParam(
2980 0 : "latitude_of_projection_origin",
2981 0 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2982 : }
2983 : }
2984 :
2985 27 : else if (EQUAL(pszProjection, SRS_PT_POLYCONIC))
2986 : {
2987 1 : pszPDS4ProjectionName = "Polyconic";
2988 1 : aoProjParams.push_back(ProjParam(
2989 0 : "longitude_of_central_meridian",
2990 1 : m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0)));
2991 1 : aoProjParams.push_back(ProjParam(
2992 0 : "latitude_of_projection_origin",
2993 2 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2994 : }
2995 26 : else if (EQUAL(pszProjection, SRS_PT_SINUSOIDAL))
2996 : {
2997 2 : pszPDS4ProjectionName = "Sinusoidal";
2998 2 : aoProjParams.push_back(ProjParam(
2999 0 : "longitude_of_central_meridian",
3000 2 : FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
3001 2 : aoProjParams.push_back(ProjParam(
3002 0 : "latitude_of_projection_origin",
3003 4 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
3004 : }
3005 :
3006 24 : else if (EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
3007 : {
3008 18 : pszPDS4ProjectionName = "Transverse Mercator";
3009 18 : aoProjParams.push_back(
3010 0 : ProjParam("scale_factor_at_central_meridian",
3011 18 : m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
3012 18 : aoProjParams.push_back(ProjParam(
3013 0 : "longitude_of_central_meridian",
3014 18 : m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0)));
3015 18 : aoProjParams.push_back(ProjParam(
3016 0 : "latitude_of_projection_origin",
3017 36 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
3018 : }
3019 6 : else if (EQUAL(pszProjection, SRS_PT_ORTHOGRAPHIC))
3020 : {
3021 1 : pszPDS4ProjectionName = "Orthographic";
3022 1 : aoProjParams.push_back(ProjParam(
3023 0 : "longitude_of_central_meridian",
3024 1 : FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
3025 1 : aoProjParams.push_back(ProjParam(
3026 0 : "latitude_of_projection_origin",
3027 2 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
3028 : }
3029 :
3030 5 : else if (EQUAL(pszProjection, SRS_PT_MERCATOR_1SP))
3031 : {
3032 2 : pszPDS4ProjectionName = "Mercator";
3033 2 : aoProjParams.push_back(ProjParam(
3034 0 : "longitude_of_central_meridian",
3035 2 : FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
3036 2 : aoProjParams.push_back(ProjParam(
3037 0 : "latitude_of_projection_origin",
3038 2 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
3039 2 : aoProjParams.push_back(
3040 0 : ProjParam("scale_factor_at_projection_origin",
3041 4 : m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
3042 : }
3043 :
3044 3 : else if (EQUAL(pszProjection, SRS_PT_MERCATOR_2SP))
3045 : {
3046 1 : pszPDS4ProjectionName = "Mercator";
3047 1 : aoProjParams.push_back(ProjParam(
3048 0 : "standard_parallel_1",
3049 1 : m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0)));
3050 1 : aoProjParams.push_back(ProjParam(
3051 0 : "longitude_of_central_meridian",
3052 1 : FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
3053 1 : aoProjParams.push_back(ProjParam(
3054 0 : "latitude_of_projection_origin",
3055 2 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
3056 : }
3057 :
3058 2 : else if (EQUAL(pszProjection, SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA))
3059 : {
3060 1 : pszPDS4ProjectionName = "Lambert Azimuthal Equal Area";
3061 1 : aoProjParams.push_back(ProjParam(
3062 0 : "longitude_of_central_meridian",
3063 1 : FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
3064 1 : aoProjParams.push_back(ProjParam(
3065 0 : "latitude_of_projection_origin",
3066 2 : m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
3067 : }
3068 :
3069 1 : else if (EQUAL(pszProjection, "custom_proj4"))
3070 : {
3071 : const char *pszProj4 =
3072 1 : m_oSRS.GetExtension("PROJCS", "PROJ4", nullptr);
3073 1 : if (pszProj4 && strstr(pszProj4, "+proj=ob_tran") &&
3074 1 : strstr(pszProj4, "+o_proj=eqc"))
3075 : {
3076 1 : pszPDS4ProjectionName = "Oblique Cylindrical";
3077 : const auto FetchParam =
3078 3 : [](const char *pszProj4Str, const char *pszKey)
3079 : {
3080 6 : CPLString needle;
3081 3 : needle.Printf("+%s=", pszKey);
3082 3 : const char *pszVal = strstr(pszProj4Str, needle.c_str());
3083 3 : if (pszVal)
3084 3 : return CPLAtof(pszVal + needle.size());
3085 0 : return 0.0;
3086 : };
3087 :
3088 1 : double dfLonP = FetchParam(pszProj4, "o_lon_p");
3089 1 : double dfLatP = FetchParam(pszProj4, "o_lat_p");
3090 1 : double dfLon0 = FetchParam(pszProj4, "lon_0");
3091 1 : double dfPoleRotation = -dfLonP;
3092 1 : double dfPoleLatitude = 180 - dfLatP;
3093 1 : double dfPoleLongitude = dfLon0;
3094 :
3095 1 : aoProjParams.push_back(ProjParam("map_projection_rotation",
3096 : dfMapProjectionRotation));
3097 1 : aoProjParams.push_back(
3098 1 : ProjParam("oblique_proj_pole_latitude", dfPoleLatitude));
3099 1 : aoProjParams.push_back(ProjParam("oblique_proj_pole_longitude",
3100 1 : FixLong(dfPoleLongitude)));
3101 1 : aoProjParams.push_back(
3102 2 : ProjParam("oblique_proj_pole_rotation", dfPoleRotation));
3103 : }
3104 : else
3105 : {
3106 0 : CPLError(CE_Warning, CPLE_NotSupported,
3107 : "Projection %s not supported", pszProjection);
3108 : }
3109 : }
3110 : else
3111 : {
3112 0 : CPLError(CE_Warning, CPLE_NotSupported,
3113 : "Projection %s not supported", pszProjection);
3114 : }
3115 194 : CPLCreateXMLElementAndValue(psMP,
3116 194 : (osPrefix + "map_projection_name").c_str(),
3117 : pszPDS4ProjectionName);
3118 97 : CPLXMLNode *psProj = CPLCreateXMLNode(
3119 : psMP, CXT_Element,
3120 194 : CPLString(osPrefix + pszPDS4ProjectionName).replaceAll(' ', '_'));
3121 380 : for (size_t i = 0; i < aoProjParams.size(); i++)
3122 : {
3123 566 : CPLXMLNode *psParam = CPLCreateXMLElementAndValue(
3124 566 : psProj, (osPrefix + aoProjParams[i].first).c_str(),
3125 283 : CPLSPrintf("%.17g", aoProjParams[i].second));
3126 283 : if (!STARTS_WITH(aoProjParams[i].first, "scale_factor"))
3127 : {
3128 261 : CPLAddXMLAttributeAndValue(psParam, "unit", "deg");
3129 : }
3130 : }
3131 :
3132 97 : if (pszProjection &&
3133 34 : EQUAL(pszProjection, SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER))
3134 : {
3135 : CPLXMLNode *psOLA =
3136 1 : CPLCreateXMLNode(nullptr, CXT_Element,
3137 2 : (osPrefix + "Oblique_Line_Azimuth").c_str());
3138 2 : CPLAddXMLAttributeAndValue(
3139 : CPLCreateXMLElementAndValue(
3140 2 : psOLA, (osPrefix + "azimuthal_angle").c_str(),
3141 : CPLSPrintf("%.17g",
3142 : m_oSRS.GetNormProjParm(SRS_PP_AZIMUTH, 0.0))),
3143 : "unit", "deg");
3144 : ;
3145 : // Not completely sure of this
3146 2 : CPLAddXMLAttributeAndValue(
3147 : CPLCreateXMLElementAndValue(
3148 : psOLA,
3149 2 : (osPrefix + "azimuth_measure_point_longitude").c_str(),
3150 : CPLSPrintf("%.17g", FixLong(m_oSRS.GetNormProjParm(
3151 : SRS_PP_CENTRAL_MERIDIAN, 0.0)))),
3152 : "unit", "deg");
3153 :
3154 1 : if (bUse_CART_1933_Or_Later)
3155 : {
3156 1 : CPLAddXMLChild(psProj, psOLA);
3157 :
3158 1 : CPLAddXMLAttributeAndValue(
3159 : CPLCreateXMLElementAndValue(
3160 : psProj,
3161 2 : (osPrefix + "longitude_of_central_meridian").c_str(),
3162 : "0"),
3163 : "unit", "deg");
3164 :
3165 : const double dfScaleFactor =
3166 1 : m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 0.0);
3167 1 : if (dfScaleFactor != 1.0)
3168 : {
3169 0 : CPLError(CE_Warning, CPLE_NotSupported,
3170 : "Scale factor on initial support = %.17g cannot "
3171 : "be encoded in PDS4",
3172 : dfScaleFactor);
3173 : }
3174 : }
3175 : else
3176 : {
3177 0 : CPLCreateXMLElementAndValue(
3178 : psProj,
3179 0 : (osPrefix + "scale_factor_at_projection_origin").c_str(),
3180 : CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
3181 : SRS_PP_SCALE_FACTOR, 0.0)));
3182 :
3183 0 : CPLAddXMLChild(psProj, psOLA);
3184 : }
3185 :
3186 2 : CPLAddXMLAttributeAndValue(
3187 : CPLCreateXMLElementAndValue(
3188 : psProj,
3189 2 : (osPrefix + "latitude_of_projection_origin").c_str(),
3190 : CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
3191 : SRS_PP_LATITUDE_OF_ORIGIN, 0.0))),
3192 1 : "unit", "deg");
3193 : }
3194 96 : else if (pszProjection &&
3195 33 : EQUAL(pszProjection,
3196 : SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN))
3197 : {
3198 1 : if (bUse_CART_1933_Or_Later)
3199 : {
3200 : const double dfScaleFactor =
3201 1 : m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 0.0);
3202 1 : if (dfScaleFactor != 1.0)
3203 : {
3204 0 : CPLError(CE_Warning, CPLE_NotSupported,
3205 : "Scale factor on initial support = %.17g cannot "
3206 : "be encoded in PDS4",
3207 : dfScaleFactor);
3208 : }
3209 : }
3210 : else
3211 : {
3212 0 : CPLCreateXMLElementAndValue(
3213 : psProj,
3214 0 : (osPrefix + "scale_factor_at_projection_origin").c_str(),
3215 : CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
3216 : SRS_PP_SCALE_FACTOR, 0.0)));
3217 : }
3218 :
3219 1 : CPLXMLNode *psOLP = CPLCreateXMLNode(
3220 2 : psProj, CXT_Element, (osPrefix + "Oblique_Line_Point").c_str());
3221 1 : CPLXMLNode *psOLPG1 = CPLCreateXMLNode(
3222 : psOLP, CXT_Element,
3223 2 : (osPrefix + "Oblique_Line_Point_Group").c_str());
3224 2 : CPLAddXMLAttributeAndValue(
3225 : CPLCreateXMLElementAndValue(
3226 2 : psOLPG1, (osPrefix + "oblique_line_latitude").c_str(),
3227 : CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
3228 : SRS_PP_LATITUDE_OF_POINT_1, 0.0))),
3229 : "unit", "deg");
3230 2 : CPLAddXMLAttributeAndValue(
3231 : CPLCreateXMLElementAndValue(
3232 2 : psOLPG1, (osPrefix + "oblique_line_longitude").c_str(),
3233 : CPLSPrintf("%.17g",
3234 : FixLong(m_oSRS.GetNormProjParm(
3235 : SRS_PP_LONGITUDE_OF_POINT_1, 0.0)))),
3236 : "unit", "deg");
3237 1 : CPLXMLNode *psOLPG2 = CPLCreateXMLNode(
3238 : psOLP, CXT_Element,
3239 2 : (osPrefix + "Oblique_Line_Point_Group").c_str());
3240 2 : CPLAddXMLAttributeAndValue(
3241 : CPLCreateXMLElementAndValue(
3242 2 : psOLPG2, (osPrefix + "oblique_line_latitude").c_str(),
3243 : CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
3244 : SRS_PP_LATITUDE_OF_POINT_2, 0.0))),
3245 : "unit", "deg");
3246 2 : CPLAddXMLAttributeAndValue(
3247 : CPLCreateXMLElementAndValue(
3248 2 : psOLPG2, (osPrefix + "oblique_line_longitude").c_str(),
3249 : CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
3250 : SRS_PP_LONGITUDE_OF_POINT_2, 0.0))),
3251 : "unit", "deg");
3252 :
3253 1 : if (bUse_CART_1933_Or_Later)
3254 : {
3255 1 : CPLAddXMLAttributeAndValue(
3256 : CPLCreateXMLElementAndValue(
3257 : psProj,
3258 2 : (osPrefix + "longitude_of_central_meridian").c_str(),
3259 : "0"),
3260 : "unit", "deg");
3261 : }
3262 :
3263 2 : CPLAddXMLAttributeAndValue(
3264 : CPLCreateXMLElementAndValue(
3265 : psProj,
3266 2 : (osPrefix + "latitude_of_projection_origin").c_str(),
3267 : CPLSPrintf("%.17g", FixLong(m_oSRS.GetNormProjParm(
3268 : SRS_PP_LATITUDE_OF_ORIGIN, 0.0)))),
3269 : "unit", "deg");
3270 : }
3271 :
3272 97 : CPLXMLNode *psCR = nullptr;
3273 97 : if (m_bGotTransform || !IsCARTVersionGTE(pszCARTVersion, "1B00"))
3274 : {
3275 96 : CPLXMLNode *psPCI = CPLCreateXMLNode(
3276 : psPlanar, CXT_Element,
3277 192 : (osPrefix + "Planar_Coordinate_Information").c_str());
3278 96 : CPLCreateXMLElementAndValue(
3279 192 : psPCI, (osPrefix + "planar_coordinate_encoding_method").c_str(),
3280 : "Coordinate Pair");
3281 96 : psCR = CPLCreateXMLNode(
3282 : psPCI, CXT_Element,
3283 192 : (osPrefix + "Coordinate_Representation").c_str());
3284 : }
3285 97 : const double dfLinearUnits = m_oSRS.GetLinearUnits();
3286 97 : const double dfDegToMeter = m_oSRS.GetSemiMajor() * M_PI / 180.0;
3287 :
3288 97 : if (psCR == nullptr)
3289 : {
3290 : // do nothing
3291 : }
3292 96 : else if (!m_bGotTransform)
3293 : {
3294 0 : CPLAddXMLAttributeAndValue(
3295 : CPLCreateXMLElementAndValue(
3296 0 : psCR, (osPrefix + "pixel_resolution_x").c_str(), "0"),
3297 : "unit", "m/pixel");
3298 0 : CPLAddXMLAttributeAndValue(
3299 : CPLCreateXMLElementAndValue(
3300 0 : psCR, (osPrefix + "pixel_resolution_y").c_str(), "0"),
3301 : "unit", "m/pixel");
3302 0 : CPLAddXMLAttributeAndValue(
3303 : CPLCreateXMLElementAndValue(
3304 0 : psCR, (osPrefix + "pixel_scale_x").c_str(), "0"),
3305 : "unit", "pixel/deg");
3306 0 : CPLAddXMLAttributeAndValue(
3307 : CPLCreateXMLElementAndValue(
3308 0 : psCR, (osPrefix + "pixel_scale_y").c_str(), "0"),
3309 : "unit", "pixel/deg");
3310 : }
3311 96 : else if (m_oSRS.IsGeographic())
3312 : {
3313 126 : CPLAddXMLAttributeAndValue(
3314 : CPLCreateXMLElementAndValue(
3315 126 : psCR, (osPrefix + "pixel_resolution_x").c_str(),
3316 : CPLSPrintf("%.17g", dfUnrotatedResX * dfDegToMeter)),
3317 : "unit", "m/pixel");
3318 63 : CPLAddXMLAttributeAndValue(
3319 : CPLCreateXMLElementAndValue(
3320 126 : psCR, (osPrefix + "pixel_resolution_y").c_str(),
3321 63 : CPLSPrintf("%.17g", -dfUnrotatedResY * dfDegToMeter)),
3322 : "unit", "m/pixel");
3323 126 : CPLAddXMLAttributeAndValue(
3324 : CPLCreateXMLElementAndValue(
3325 126 : psCR, (osPrefix + "pixel_scale_x").c_str(),
3326 : CPLSPrintf("%.17g", 1.0 / (dfUnrotatedResX))),
3327 : "unit", "pixel/deg");
3328 126 : CPLAddXMLAttributeAndValue(
3329 : CPLCreateXMLElementAndValue(
3330 126 : psCR, (osPrefix + "pixel_scale_y").c_str(),
3331 : CPLSPrintf("%.17g", 1.0 / (-dfUnrotatedResY))),
3332 : "unit", "pixel/deg");
3333 : }
3334 33 : else if (m_oSRS.IsProjected())
3335 : {
3336 66 : CPLAddXMLAttributeAndValue(
3337 : CPLCreateXMLElementAndValue(
3338 66 : psCR, (osPrefix + "pixel_resolution_x").c_str(),
3339 : CPLSPrintf("%.17g", dfUnrotatedResX * dfLinearUnits)),
3340 : "unit", "m/pixel");
3341 33 : CPLAddXMLAttributeAndValue(
3342 : CPLCreateXMLElementAndValue(
3343 66 : psCR, (osPrefix + "pixel_resolution_y").c_str(),
3344 33 : CPLSPrintf("%.17g", -dfUnrotatedResY * dfLinearUnits)),
3345 : "unit", "m/pixel");
3346 33 : CPLAddXMLAttributeAndValue(
3347 : CPLCreateXMLElementAndValue(
3348 66 : psCR, (osPrefix + "pixel_scale_x").c_str(),
3349 : CPLSPrintf("%.17g", dfDegToMeter /
3350 33 : (dfUnrotatedResX * dfLinearUnits))),
3351 : "unit", "pixel/deg");
3352 33 : CPLAddXMLAttributeAndValue(
3353 : CPLCreateXMLElementAndValue(
3354 66 : psCR, (osPrefix + "pixel_scale_y").c_str(),
3355 33 : CPLSPrintf("%.17g", dfDegToMeter / (-dfUnrotatedResY *
3356 : dfLinearUnits))),
3357 : "unit", "pixel/deg");
3358 : }
3359 :
3360 97 : if (m_bGotTransform)
3361 : {
3362 : CPLXMLNode *psGT =
3363 96 : CPLCreateXMLNode(psPlanar, CXT_Element,
3364 192 : (osPrefix + "Geo_Transformation").c_str());
3365 : const double dfFalseEasting =
3366 96 : m_oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
3367 : const double dfFalseNorthing =
3368 96 : m_oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
3369 96 : const double dfULX = -dfFalseEasting + dfUnrotatedULX;
3370 96 : const double dfULY = -dfFalseNorthing + dfUnrotatedULY;
3371 96 : if (m_oSRS.IsGeographic())
3372 : {
3373 126 : CPLAddXMLAttributeAndValue(
3374 : CPLCreateXMLElementAndValue(
3375 126 : psGT, (osPrefix + "upperleft_corner_x").c_str(),
3376 : CPLSPrintf("%.17g", dfULX * dfDegToMeter)),
3377 : "unit", "m");
3378 126 : CPLAddXMLAttributeAndValue(
3379 : CPLCreateXMLElementAndValue(
3380 126 : psGT, (osPrefix + "upperleft_corner_y").c_str(),
3381 : CPLSPrintf("%.17g", dfULY * dfDegToMeter)),
3382 : "unit", "m");
3383 : }
3384 33 : else if (m_oSRS.IsProjected())
3385 : {
3386 66 : CPLAddXMLAttributeAndValue(
3387 : CPLCreateXMLElementAndValue(
3388 66 : psGT, (osPrefix + "upperleft_corner_x").c_str(),
3389 : CPLSPrintf("%.17g", dfULX * dfLinearUnits)),
3390 : "unit", "m");
3391 66 : CPLAddXMLAttributeAndValue(
3392 : CPLCreateXMLElementAndValue(
3393 66 : psGT, (osPrefix + "upperleft_corner_y").c_str(),
3394 : CPLSPrintf("%.17g", dfULY * dfLinearUnits)),
3395 : "unit", "m");
3396 : }
3397 : }
3398 : }
3399 : else
3400 : {
3401 1 : CPLXMLNode *psGeographic = CPLCreateXMLNode(
3402 2 : psHCSD, CXT_Element, (osPrefix + "Geographic").c_str());
3403 1 : if (!IsCARTVersionGTE(pszCARTVersion, "1B00"))
3404 : {
3405 0 : CPLAddXMLAttributeAndValue(
3406 : CPLCreateXMLElementAndValue(
3407 0 : psGeographic, (osPrefix + "latitude_resolution").c_str(),
3408 : "0"),
3409 : "unit", "deg");
3410 0 : CPLAddXMLAttributeAndValue(
3411 : CPLCreateXMLElementAndValue(
3412 0 : psGeographic, (osPrefix + "longitude_resolution").c_str(),
3413 : "0"),
3414 : "unit", "deg");
3415 : }
3416 : }
3417 :
3418 98 : CPLXMLNode *psGM = CPLCreateXMLNode(psHCSD, CXT_Element,
3419 196 : (osPrefix + "Geodetic_Model").c_str());
3420 196 : const char *pszLatitudeType = CSLFetchNameValueDef(
3421 98 : m_papszCreationOptions, "LATITUDE_TYPE", "Planetocentric");
3422 : // Fix case
3423 98 : if (EQUAL(pszLatitudeType, "Planetocentric"))
3424 97 : pszLatitudeType = "Planetocentric";
3425 1 : else if (EQUAL(pszLatitudeType, "Planetographic"))
3426 1 : pszLatitudeType = "Planetographic";
3427 98 : CPLCreateXMLElementAndValue(psGM, (osPrefix + "latitude_type").c_str(),
3428 : pszLatitudeType);
3429 :
3430 98 : const char *pszDatum = m_oSRS.GetAttrValue("DATUM");
3431 98 : if (pszDatum && STARTS_WITH(pszDatum, "D_"))
3432 : {
3433 4 : CPLCreateXMLElementAndValue(psGM, (osPrefix + "spheroid_name").c_str(),
3434 : pszDatum + 2);
3435 : }
3436 94 : else if (pszDatum)
3437 : {
3438 94 : CPLCreateXMLElementAndValue(psGM, (osPrefix + "spheroid_name").c_str(),
3439 : pszDatum);
3440 : }
3441 :
3442 98 : double dfSemiMajor = m_oSRS.GetSemiMajor();
3443 98 : double dfSemiMinor = m_oSRS.GetSemiMinor();
3444 98 : const char *pszRadii = CSLFetchNameValue(m_papszCreationOptions, "RADII");
3445 98 : if (pszRadii)
3446 : {
3447 1 : char **papszTokens = CSLTokenizeString2(pszRadii, " ,", 0);
3448 1 : if (CSLCount(papszTokens) == 2)
3449 : {
3450 1 : dfSemiMajor = CPLAtof(papszTokens[0]);
3451 1 : dfSemiMinor = CPLAtof(papszTokens[1]);
3452 : }
3453 1 : CSLDestroy(papszTokens);
3454 : }
3455 :
3456 : const bool bUseLDD1930RadiusNames =
3457 98 : IsCARTVersionGTE(pszCARTVersion, "1B10_1930");
3458 :
3459 196 : CPLAddXMLAttributeAndValue(
3460 : CPLCreateXMLElementAndValue(
3461 : psGM,
3462 196 : (osPrefix +
3463 : (bUseLDD1930RadiusNames ? "a_axis_radius" : "semi_major_radius"))
3464 : .c_str(),
3465 : CPLSPrintf("%.17g", dfSemiMajor)),
3466 : "unit", "m");
3467 : // No, this is not a bug. The PDS4 b_axis_radius/semi_minor_radius is the
3468 : // minor radius on the equatorial plane. Which in WKT doesn't really exist,
3469 : // so reuse the WKT semi major
3470 196 : CPLAddXMLAttributeAndValue(
3471 : CPLCreateXMLElementAndValue(
3472 : psGM,
3473 196 : (osPrefix +
3474 : (bUseLDD1930RadiusNames ? "b_axis_radius" : "semi_minor_radius"))
3475 : .c_str(),
3476 : CPLSPrintf("%.17g", dfSemiMajor)),
3477 : "unit", "m");
3478 196 : CPLAddXMLAttributeAndValue(
3479 : CPLCreateXMLElementAndValue(
3480 : psGM,
3481 196 : (osPrefix +
3482 : (bUseLDD1930RadiusNames ? "c_axis_radius" : "polar_radius"))
3483 : .c_str(),
3484 : CPLSPrintf("%.17g", dfSemiMinor)),
3485 : "unit", "m");
3486 :
3487 : // Fix case
3488 98 : if (EQUAL(pszLongitudeDirection, "Positive East"))
3489 97 : pszLongitudeDirection = "Positive East";
3490 1 : else if (EQUAL(pszLongitudeDirection, "Positive West"))
3491 1 : pszLongitudeDirection = "Positive West";
3492 98 : CPLCreateXMLElementAndValue(psGM,
3493 196 : (osPrefix + "longitude_direction").c_str(),
3494 : pszLongitudeDirection);
3495 98 : }
3496 :
3497 : /************************************************************************/
3498 : /* SubstituteVariables() */
3499 : /************************************************************************/
3500 :
3501 16490 : void PDS4Dataset::SubstituteVariables(CPLXMLNode *psNode, char **papszDict)
3502 : {
3503 16490 : if (psNode->eType == CXT_Text && psNode->pszValue &&
3504 6668 : strstr(psNode->pszValue, "${"))
3505 : {
3506 1382 : CPLString osVal(psNode->pszValue);
3507 :
3508 1556 : if (strstr(psNode->pszValue, "${TITLE}") != nullptr &&
3509 174 : CSLFetchNameValue(papszDict, "VAR_TITLE") == nullptr)
3510 : {
3511 160 : const CPLString osTitle(CPLGetFilename(GetDescription()));
3512 160 : CPLError(CE_Warning, CPLE_AppDefined,
3513 : "VAR_TITLE not defined. Using %s by default",
3514 : osTitle.c_str());
3515 160 : osVal.replaceAll("${TITLE}", osTitle);
3516 : }
3517 :
3518 4599 : for (char **papszIter = papszDict; papszIter && *papszIter; papszIter++)
3519 : {
3520 3217 : if (STARTS_WITH_CI(*papszIter, "VAR_"))
3521 : {
3522 2746 : char *pszKey = nullptr;
3523 2746 : const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
3524 2746 : if (pszKey && pszValue)
3525 : {
3526 2746 : const char *pszVarName = pszKey + strlen("VAR_");
3527 : osVal.replaceAll(
3528 2746 : (CPLString("${") + pszVarName + "}").c_str(), pszValue);
3529 : osVal.replaceAll(
3530 5492 : CPLString(CPLString("${") + pszVarName + "}")
3531 2746 : .tolower()
3532 : .c_str(),
3533 8238 : CPLString(pszValue).tolower());
3534 2746 : CPLFree(pszKey);
3535 : }
3536 : }
3537 : }
3538 1382 : if (osVal.find("${") != std::string::npos)
3539 : {
3540 778 : CPLError(CE_Warning, CPLE_AppDefined, "%s could not be substituted",
3541 : osVal.c_str());
3542 : }
3543 1382 : CPLFree(psNode->pszValue);
3544 1382 : psNode->pszValue = CPLStrdup(osVal);
3545 : }
3546 :
3547 32799 : for (CPLXMLNode *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
3548 : {
3549 16309 : SubstituteVariables(psIter, papszDict);
3550 : }
3551 16490 : }
3552 :
3553 : /************************************************************************/
3554 : /* InitImageFile() */
3555 : /************************************************************************/
3556 :
3557 93 : bool PDS4Dataset::InitImageFile()
3558 : {
3559 93 : m_bMustInitImageFile = false;
3560 :
3561 93 : int bHasNoData = FALSE;
3562 93 : double dfNoData = 0;
3563 93 : int bHasNoDataAsInt64 = FALSE;
3564 93 : int64_t nNoDataInt64 = 0;
3565 93 : int bHasNoDataAsUInt64 = FALSE;
3566 93 : uint64_t nNoDataUInt64 = 0;
3567 93 : const GDALDataType eDT = GetRasterBand(1)->GetRasterDataType();
3568 93 : if (eDT == GDT_Int64)
3569 : {
3570 4 : nNoDataInt64 =
3571 4 : GetRasterBand(1)->GetNoDataValueAsInt64(&bHasNoDataAsInt64);
3572 4 : if (!bHasNoDataAsInt64)
3573 2 : nNoDataInt64 = 0;
3574 : }
3575 89 : else if (eDT == GDT_UInt64)
3576 : {
3577 4 : nNoDataUInt64 =
3578 4 : GetRasterBand(1)->GetNoDataValueAsUInt64(&bHasNoDataAsUInt64);
3579 4 : if (!bHasNoDataAsUInt64)
3580 2 : nNoDataUInt64 = 0;
3581 : }
3582 : else
3583 : {
3584 85 : dfNoData = GetRasterBand(1)->GetNoDataValue(&bHasNoData);
3585 85 : if (!bHasNoData)
3586 69 : dfNoData = 0;
3587 : }
3588 :
3589 93 : if (m_poExternalDS)
3590 : {
3591 : int nBlockXSize, nBlockYSize;
3592 18 : GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
3593 18 : const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
3594 18 : const int nBlockSizeBytes = nBlockXSize * nBlockYSize * nDTSize;
3595 18 : const int l_nBlocksPerColumn = DIV_ROUND_UP(nRasterYSize, nBlockYSize);
3596 :
3597 18 : if (nBands == 1 || EQUAL(m_osInterleave, "BSQ"))
3598 : {
3599 : // We need to make sure that blocks are written in the right order
3600 38 : for (int i = 0; i < nBands; i++)
3601 : {
3602 21 : if (eDT == GDT_Int64)
3603 : {
3604 1 : if (m_poExternalDS->GetRasterBand(i + 1)->RasterIO(
3605 : GF_Write, 0, 0, nRasterXSize, nRasterYSize,
3606 1 : &nNoDataInt64, 1, 1, eDT, 0, 0, nullptr) != CE_None)
3607 : {
3608 0 : return false;
3609 : }
3610 : }
3611 20 : else if (eDT == GDT_UInt64)
3612 : {
3613 1 : if (m_poExternalDS->GetRasterBand(i + 1)->RasterIO(
3614 : GF_Write, 0, 0, nRasterXSize, nRasterYSize,
3615 : &nNoDataUInt64, 1, 1, eDT, 0, 0,
3616 1 : nullptr) != CE_None)
3617 : {
3618 0 : return false;
3619 : }
3620 : }
3621 : else
3622 : {
3623 19 : if (m_poExternalDS->GetRasterBand(i + 1)->Fill(dfNoData) !=
3624 : CE_None)
3625 : {
3626 0 : return false;
3627 : }
3628 : }
3629 : }
3630 17 : m_poExternalDS->FlushCache(false);
3631 :
3632 : // Check that blocks are effectively written in expected order.
3633 17 : GIntBig nLastOffset = 0;
3634 38 : for (int i = 0; i < nBands; i++)
3635 : {
3636 333 : for (int y = 0; y < l_nBlocksPerColumn; y++)
3637 : {
3638 : const char *pszBlockOffset =
3639 624 : m_poExternalDS->GetRasterBand(i + 1)->GetMetadataItem(
3640 312 : CPLSPrintf("BLOCK_OFFSET_%d_%d", 0, y), "TIFF");
3641 312 : if (pszBlockOffset)
3642 : {
3643 312 : GIntBig nOffset = CPLAtoGIntBig(pszBlockOffset);
3644 312 : if (i != 0 || y != 0)
3645 : {
3646 295 : if (nOffset != nLastOffset + nBlockSizeBytes)
3647 : {
3648 0 : CPLError(CE_Warning, CPLE_AppDefined,
3649 : "Block %d,%d band %d not at expected "
3650 : "offset",
3651 : 0, y, i + 1);
3652 0 : return false;
3653 : }
3654 : }
3655 312 : nLastOffset = nOffset;
3656 : }
3657 : else
3658 : {
3659 0 : CPLError(CE_Warning, CPLE_AppDefined,
3660 : "Block %d,%d band %d not at expected "
3661 : "offset",
3662 : 0, y, i + 1);
3663 0 : return false;
3664 : }
3665 : }
3666 : }
3667 : }
3668 : else
3669 : {
3670 1 : void *pBlockData = VSI_MALLOC_VERBOSE(nBlockSizeBytes);
3671 1 : if (pBlockData == nullptr)
3672 0 : return false;
3673 1 : if (eDT == GDT_Int64)
3674 : {
3675 0 : GDALCopyWords(&nNoDataInt64, eDT, 0, pBlockData, eDT, nDTSize,
3676 : nBlockXSize * nBlockYSize);
3677 : }
3678 1 : else if (eDT == GDT_UInt64)
3679 : {
3680 0 : GDALCopyWords(&nNoDataUInt64, eDT, 0, pBlockData, eDT, nDTSize,
3681 : nBlockXSize * nBlockYSize);
3682 : }
3683 : else
3684 : {
3685 1 : GDALCopyWords(&dfNoData, GDT_Float64, 0, pBlockData, eDT,
3686 : nDTSize, nBlockXSize * nBlockYSize);
3687 : }
3688 2 : for (int y = 0; y < l_nBlocksPerColumn; y++)
3689 : {
3690 4 : for (int i = 0; i < nBands; i++)
3691 : {
3692 3 : if (m_poExternalDS->GetRasterBand(i + 1)->WriteBlock(
3693 3 : 0, y, pBlockData) != CE_None)
3694 : {
3695 0 : VSIFree(pBlockData);
3696 0 : return false;
3697 : }
3698 : }
3699 : }
3700 1 : VSIFree(pBlockData);
3701 1 : m_poExternalDS->FlushCache(false);
3702 :
3703 : // Check that blocks are effectively written in expected order.
3704 1 : GIntBig nLastOffset = 0;
3705 2 : for (int y = 0; y < l_nBlocksPerColumn; y++)
3706 : {
3707 : const char *pszBlockOffset =
3708 2 : m_poExternalDS->GetRasterBand(1)->GetMetadataItem(
3709 1 : CPLSPrintf("BLOCK_OFFSET_%d_%d", 0, y), "TIFF");
3710 1 : if (pszBlockOffset)
3711 : {
3712 1 : GIntBig nOffset = CPLAtoGIntBig(pszBlockOffset);
3713 1 : if (y != 0)
3714 : {
3715 0 : if (nOffset !=
3716 0 : nLastOffset +
3717 0 : static_cast<GIntBig>(nBlockSizeBytes) * nBands)
3718 : {
3719 0 : CPLError(CE_Warning, CPLE_AppDefined,
3720 : "Block %d,%d not at expected "
3721 : "offset",
3722 : 0, y);
3723 0 : return false;
3724 : }
3725 : }
3726 1 : nLastOffset = nOffset;
3727 : }
3728 : else
3729 : {
3730 0 : CPLError(CE_Warning, CPLE_AppDefined,
3731 : "Block %d,%d not at expected "
3732 : "offset",
3733 : 0, y);
3734 0 : return false;
3735 : }
3736 : }
3737 : }
3738 :
3739 18 : return true;
3740 : }
3741 :
3742 75 : const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
3743 75 : const vsi_l_offset nFileSize = static_cast<vsi_l_offset>(nRasterXSize) *
3744 75 : nRasterYSize * nBands * nDTSize;
3745 75 : if ((eDT == GDT_Int64 && (nNoDataInt64 == 0 || !bHasNoDataAsInt64)) ||
3746 73 : (eDT == GDT_UInt64 && (nNoDataUInt64 == 0 || !bHasNoDataAsUInt64)) ||
3747 70 : (eDT != GDT_Int64 && eDT != GDT_UInt64 &&
3748 69 : (dfNoData == 0 || !bHasNoData)))
3749 : {
3750 66 : if (VSIFTruncateL(m_fpImage, nFileSize) != 0)
3751 : {
3752 0 : CPLError(CE_Failure, CPLE_FileIO,
3753 : "Cannot create file of size " CPL_FRMT_GUIB " bytes",
3754 : nFileSize);
3755 0 : return false;
3756 : }
3757 : }
3758 : else
3759 : {
3760 9 : size_t nLineSize = static_cast<size_t>(nRasterXSize) * nDTSize;
3761 9 : void *pData = VSI_MALLOC_VERBOSE(nLineSize);
3762 9 : if (pData == nullptr)
3763 0 : return false;
3764 9 : if (eDT == GDT_Int64)
3765 : {
3766 1 : GDALCopyWords(&nNoDataInt64, eDT, 0, pData, eDT, nDTSize,
3767 : nRasterXSize);
3768 : }
3769 8 : else if (eDT == GDT_UInt64)
3770 : {
3771 1 : GDALCopyWords(&nNoDataUInt64, eDT, 0, pData, eDT, nDTSize,
3772 : nRasterXSize);
3773 : }
3774 : else
3775 : {
3776 7 : GDALCopyWords(&dfNoData, GDT_Float64, 0, pData, eDT, nDTSize,
3777 : nRasterXSize);
3778 : }
3779 : #ifdef CPL_MSB
3780 : if (GDALDataTypeIsComplex(eDT))
3781 : {
3782 : GDALSwapWords(pData, nDTSize / 2, nRasterXSize * 2, nDTSize / 2);
3783 : }
3784 : else
3785 : {
3786 : GDALSwapWords(pData, nDTSize, nRasterXSize, nDTSize);
3787 : }
3788 : #endif
3789 18 : for (vsi_l_offset i = 0;
3790 18 : i < static_cast<vsi_l_offset>(nRasterYSize) * nBands; i++)
3791 : {
3792 9 : size_t nBytesWritten = VSIFWriteL(pData, 1, nLineSize, m_fpImage);
3793 9 : if (nBytesWritten != nLineSize)
3794 : {
3795 0 : CPLError(CE_Failure, CPLE_FileIO,
3796 : "Cannot create file of size " CPL_FRMT_GUIB " bytes",
3797 : nFileSize);
3798 0 : VSIFree(pData);
3799 0 : return false;
3800 : }
3801 : }
3802 9 : VSIFree(pData);
3803 : }
3804 75 : return true;
3805 : }
3806 :
3807 : /************************************************************************/
3808 : /* GetSpecialConstants() */
3809 : /************************************************************************/
3810 :
3811 12 : static CPLXMLNode *GetSpecialConstants(const CPLString &osPrefix,
3812 : CPLXMLNode *psFileAreaObservational)
3813 : {
3814 27 : for (CPLXMLNode *psIter = psFileAreaObservational->psChild; psIter;
3815 15 : psIter = psIter->psNext)
3816 : {
3817 48 : if (psIter->eType == CXT_Element &&
3818 48 : STARTS_WITH(psIter->pszValue, (osPrefix + "Array").c_str()))
3819 : {
3820 : CPLXMLNode *psSC =
3821 11 : CPLGetXMLNode(psIter, (osPrefix + "Special_Constants").c_str());
3822 11 : if (psSC)
3823 : {
3824 9 : CPLXMLNode *psNext = psSC->psNext;
3825 9 : psSC->psNext = nullptr;
3826 9 : CPLXMLNode *psRet = CPLCloneXMLTree(psSC);
3827 9 : psSC->psNext = psNext;
3828 9 : return psRet;
3829 : }
3830 : }
3831 : }
3832 3 : return nullptr;
3833 : }
3834 :
3835 : /************************************************************************/
3836 : /* WriteHeaderAppendCase() */
3837 : /************************************************************************/
3838 :
3839 4 : void PDS4Dataset::WriteHeaderAppendCase()
3840 : {
3841 4 : CPLXMLTreeCloser oCloser(CPLParseXMLFile(GetDescription()));
3842 4 : CPLXMLNode *psRoot = oCloser.get();
3843 4 : if (psRoot == nullptr)
3844 0 : return;
3845 4 : CPLString osPrefix;
3846 4 : CPLXMLNode *psProduct = CPLGetXMLNode(psRoot, "=Product_Observational");
3847 4 : if (psProduct == nullptr)
3848 : {
3849 0 : psProduct = CPLGetXMLNode(psRoot, "=pds:Product_Observational");
3850 0 : if (psProduct)
3851 0 : osPrefix = "pds:";
3852 : }
3853 4 : if (psProduct == nullptr)
3854 : {
3855 0 : CPLError(CE_Failure, CPLE_AppDefined,
3856 : "Cannot find Product_Observational element");
3857 0 : return;
3858 : }
3859 4 : CPLXMLNode *psFAO = CPLGetXMLNode(
3860 8 : psProduct, (osPrefix + "File_Area_Observational").c_str());
3861 4 : if (psFAO == nullptr)
3862 : {
3863 0 : CPLError(CE_Failure, CPLE_AppDefined,
3864 : "Cannot find File_Area_Observational element");
3865 0 : return;
3866 : }
3867 :
3868 4 : WriteArray(osPrefix, psFAO, nullptr, nullptr);
3869 :
3870 4 : CPLSerializeXMLTreeToFile(psRoot, GetDescription());
3871 : }
3872 :
3873 : /************************************************************************/
3874 : /* WriteArray() */
3875 : /************************************************************************/
3876 :
3877 135 : void PDS4Dataset::WriteArray(const CPLString &osPrefix, CPLXMLNode *psFAO,
3878 : const char *pszLocalIdentifierDefault,
3879 : CPLXMLNode *psTemplateSpecialConstants)
3880 : {
3881 270 : const char *pszArrayType = CSLFetchNameValueDef(
3882 135 : m_papszCreationOptions, "ARRAY_TYPE", "Array_3D_Image");
3883 135 : const bool bIsArray2D = STARTS_WITH(pszArrayType, "Array_2D");
3884 : CPLXMLNode *psArray =
3885 135 : CPLCreateXMLNode(psFAO, CXT_Element, (osPrefix + pszArrayType).c_str());
3886 :
3887 270 : const char *pszLocalIdentifier = CSLFetchNameValueDef(
3888 135 : m_papszCreationOptions, "ARRAY_IDENTIFIER", pszLocalIdentifierDefault);
3889 135 : if (pszLocalIdentifier)
3890 : {
3891 131 : CPLCreateXMLElementAndValue(psArray,
3892 262 : (osPrefix + "local_identifier").c_str(),
3893 : pszLocalIdentifier);
3894 : }
3895 :
3896 135 : GUIntBig nOffset = m_nBaseOffset;
3897 135 : if (m_poExternalDS)
3898 : {
3899 : const char *pszOffset =
3900 18 : m_poExternalDS->GetRasterBand(1)->GetMetadataItem(
3901 18 : "BLOCK_OFFSET_0_0", "TIFF");
3902 18 : if (pszOffset)
3903 18 : nOffset = CPLAtoGIntBig(pszOffset);
3904 : }
3905 270 : CPLAddXMLAttributeAndValue(
3906 270 : CPLCreateXMLElementAndValue(psArray, (osPrefix + "offset").c_str(),
3907 : CPLSPrintf(CPL_FRMT_GUIB, nOffset)),
3908 : "unit", "byte");
3909 135 : CPLCreateXMLElementAndValue(psArray, (osPrefix + "axes").c_str(),
3910 : (bIsArray2D) ? "2" : "3");
3911 135 : CPLCreateXMLElementAndValue(
3912 270 : psArray, (osPrefix + "axis_index_order").c_str(), "Last Index Fastest");
3913 135 : CPLXMLNode *psElementArray = CPLCreateXMLNode(
3914 270 : psArray, CXT_Element, (osPrefix + "Element_Array").c_str());
3915 135 : GDALDataType eDT = GetRasterBand(1)->GetRasterDataType();
3916 135 : const char *pszDataType =
3917 187 : (eDT == GDT_UInt8) ? "UnsignedByte"
3918 101 : : (eDT == GDT_Int8) ? "SignedByte"
3919 91 : : (eDT == GDT_UInt16) ? "UnsignedLSB2"
3920 79 : : (eDT == GDT_Int16) ? (m_bIsLSB ? "SignedLSB2" : "SignedMSB2")
3921 70 : : (eDT == GDT_UInt32) ? (m_bIsLSB ? "UnsignedLSB4" : "UnsignedMSB4")
3922 62 : : (eDT == GDT_Int32) ? (m_bIsLSB ? "SignedLSB4" : "SignedMSB4")
3923 54 : : (eDT == GDT_UInt64) ? (m_bIsLSB ? "UnsignedLSB8" : "UnsignedMSB8")
3924 46 : : (eDT == GDT_Int64) ? (m_bIsLSB ? "SignedLSB8" : "SignedMSB8")
3925 : : (eDT == GDT_Float32)
3926 35 : ? (m_bIsLSB ? "IEEE754LSBSingle" : "IEEE754MSBSingle")
3927 : : (eDT == GDT_Float64)
3928 22 : ? (m_bIsLSB ? "IEEE754LSBDouble" : "IEEE754MSBDouble")
3929 12 : : (eDT == GDT_CFloat32) ? (m_bIsLSB ? "ComplexLSB8" : "ComplexMSB8")
3930 4 : : (eDT == GDT_CFloat64) ? (m_bIsLSB ? "ComplexLSB16" : "ComplexMSB16")
3931 : : "should not happen";
3932 135 : CPLCreateXMLElementAndValue(psElementArray,
3933 270 : (osPrefix + "data_type").c_str(), pszDataType);
3934 :
3935 135 : const char *pszUnits = GetRasterBand(1)->GetUnitType();
3936 135 : const char *pszUnitsCO = CSLFetchNameValue(m_papszCreationOptions, "UNIT");
3937 135 : if (pszUnitsCO)
3938 : {
3939 0 : pszUnits = pszUnitsCO;
3940 : }
3941 135 : if (pszUnits && pszUnits[0] != 0)
3942 : {
3943 1 : CPLCreateXMLElementAndValue(psElementArray, (osPrefix + "unit").c_str(),
3944 : pszUnits);
3945 : }
3946 :
3947 135 : int bHasScale = FALSE;
3948 135 : double dfScale = GetRasterBand(1)->GetScale(&bHasScale);
3949 135 : if (bHasScale && dfScale != 1.0)
3950 : {
3951 10 : CPLCreateXMLElementAndValue(psElementArray,
3952 10 : (osPrefix + "scaling_factor").c_str(),
3953 : CPLSPrintf("%.17g", dfScale));
3954 : }
3955 :
3956 135 : int bHasOffset = FALSE;
3957 135 : double dfOffset = GetRasterBand(1)->GetOffset(&bHasOffset);
3958 135 : if (bHasOffset && dfOffset != 1.0)
3959 : {
3960 10 : CPLCreateXMLElementAndValue(psElementArray,
3961 10 : (osPrefix + "value_offset").c_str(),
3962 : CPLSPrintf("%.17g", dfOffset));
3963 : }
3964 :
3965 : // Axis definitions
3966 : {
3967 135 : CPLXMLNode *psAxis = CPLCreateXMLNode(
3968 270 : psArray, CXT_Element, (osPrefix + "Axis_Array").c_str());
3969 270 : CPLCreateXMLElementAndValue(
3970 270 : psAxis, (osPrefix + "axis_name").c_str(),
3971 135 : EQUAL(m_osInterleave, "BSQ")
3972 : ? "Band"
3973 : :
3974 : /* EQUAL(m_osInterleave, "BIL") ? "Line" : */
3975 : "Line");
3976 270 : CPLCreateXMLElementAndValue(
3977 270 : psAxis, (osPrefix + "elements").c_str(),
3978 : CPLSPrintf("%d",
3979 135 : EQUAL(m_osInterleave, "BSQ")
3980 : ? nBands
3981 : :
3982 : /* EQUAL(m_osInterleave, "BIL") ? nRasterYSize : */
3983 : nRasterYSize));
3984 135 : CPLCreateXMLElementAndValue(
3985 270 : psAxis, (osPrefix + "sequence_number").c_str(), "1");
3986 : }
3987 : {
3988 135 : CPLXMLNode *psAxis = CPLCreateXMLNode(
3989 270 : psArray, CXT_Element, (osPrefix + "Axis_Array").c_str());
3990 135 : CPLCreateXMLElementAndValue(psAxis, (osPrefix + "axis_name").c_str(),
3991 135 : EQUAL(m_osInterleave, "BSQ") ? "Line"
3992 11 : : EQUAL(m_osInterleave, "BIL") ? "Band"
3993 : : "Sample");
3994 270 : CPLCreateXMLElementAndValue(
3995 270 : psAxis, (osPrefix + "elements").c_str(),
3996 135 : CPLSPrintf("%d", EQUAL(m_osInterleave, "BSQ") ? nRasterYSize
3997 11 : : EQUAL(m_osInterleave, "BIL") ? nBands
3998 : : nRasterXSize));
3999 135 : CPLCreateXMLElementAndValue(
4000 270 : psAxis, (osPrefix + "sequence_number").c_str(), "2");
4001 : }
4002 135 : if (!bIsArray2D)
4003 : {
4004 134 : CPLXMLNode *psAxis = CPLCreateXMLNode(
4005 268 : psArray, CXT_Element, (osPrefix + "Axis_Array").c_str());
4006 134 : CPLCreateXMLElementAndValue(psAxis, (osPrefix + "axis_name").c_str(),
4007 134 : EQUAL(m_osInterleave, "BSQ") ? "Sample"
4008 10 : : EQUAL(m_osInterleave, "BIL") ? "Sample"
4009 : : "Band");
4010 268 : CPLCreateXMLElementAndValue(
4011 268 : psAxis, (osPrefix + "elements").c_str(),
4012 134 : CPLSPrintf("%d", EQUAL(m_osInterleave, "BSQ") ? nRasterXSize
4013 10 : : EQUAL(m_osInterleave, "BIL") ? nRasterXSize
4014 : : nBands));
4015 134 : CPLCreateXMLElementAndValue(
4016 268 : psAxis, (osPrefix + "sequence_number").c_str(), "3");
4017 : }
4018 :
4019 135 : int bHasNoData = FALSE;
4020 135 : int64_t nNoDataInt64 = 0;
4021 135 : uint64_t nNoDataUInt64 = 0;
4022 135 : double dfNoData = 0;
4023 135 : if (eDT == GDT_Int64)
4024 4 : nNoDataInt64 = GetRasterBand(1)->GetNoDataValueAsInt64(&bHasNoData);
4025 131 : else if (eDT == GDT_UInt64)
4026 4 : nNoDataUInt64 = GetRasterBand(1)->GetNoDataValueAsUInt64(&bHasNoData);
4027 : else
4028 127 : dfNoData = GetRasterBand(1)->GetNoDataValue(&bHasNoData);
4029 :
4030 270 : std::string osNoData;
4031 135 : if (bHasNoData)
4032 : {
4033 29 : if (eDT == GDT_Int64)
4034 2 : osNoData = std::to_string(nNoDataInt64);
4035 27 : else if (eDT == GDT_UInt64)
4036 2 : osNoData = std::to_string(nNoDataUInt64);
4037 25 : else if (GDALDataTypeIsInteger(GetRasterBand(1)->GetRasterDataType()))
4038 20 : osNoData = std::to_string(static_cast<int64_t>(dfNoData));
4039 5 : else if (eDT == GDT_Float32)
4040 : {
4041 : uint32_t nVal;
4042 3 : float fVal = static_cast<float>(dfNoData);
4043 3 : memcpy(&nVal, &fVal, sizeof(nVal));
4044 3 : osNoData = CPLSPrintf("0x%08X", nVal);
4045 : }
4046 : else
4047 : {
4048 : uint64_t nVal;
4049 2 : memcpy(&nVal, &dfNoData, sizeof(nVal));
4050 : osNoData =
4051 2 : CPLSPrintf("0x%16llX", static_cast<unsigned long long>(nVal));
4052 : }
4053 : }
4054 :
4055 135 : if (psTemplateSpecialConstants)
4056 : {
4057 9 : CPLAddXMLChild(psArray, psTemplateSpecialConstants);
4058 9 : if (bHasNoData)
4059 : {
4060 : CPLXMLNode *psMC =
4061 6 : CPLGetXMLNode(psTemplateSpecialConstants,
4062 12 : (osPrefix + "missing_constant").c_str());
4063 6 : if (psMC != nullptr)
4064 : {
4065 4 : if (psMC->psChild && psMC->psChild->eType == CXT_Text)
4066 : {
4067 4 : CPLFree(psMC->psChild->pszValue);
4068 4 : psMC->psChild->pszValue = CPLStrdup(osNoData.c_str());
4069 : }
4070 : }
4071 : else
4072 : {
4073 : CPLXMLNode *psSaturatedConstant =
4074 2 : CPLGetXMLNode(psTemplateSpecialConstants,
4075 4 : (osPrefix + "saturated_constant").c_str());
4076 4 : psMC = CPLCreateXMLElementAndValue(
4077 4 : nullptr, (osPrefix + "missing_constant").c_str(),
4078 : osNoData.c_str());
4079 : CPLXMLNode *psNext;
4080 2 : if (psSaturatedConstant)
4081 : {
4082 1 : psNext = psSaturatedConstant->psNext;
4083 1 : psSaturatedConstant->psNext = psMC;
4084 : }
4085 : else
4086 : {
4087 1 : psNext = psTemplateSpecialConstants->psChild;
4088 1 : psTemplateSpecialConstants->psChild = psMC;
4089 : }
4090 2 : psMC->psNext = psNext;
4091 : }
4092 : }
4093 : }
4094 126 : else if (bHasNoData)
4095 : {
4096 23 : CPLXMLNode *psSC = CPLCreateXMLNode(
4097 46 : psArray, CXT_Element, (osPrefix + "Special_Constants").c_str());
4098 46 : CPLCreateXMLElementAndValue(
4099 46 : psSC, (osPrefix + "missing_constant").c_str(), osNoData.c_str());
4100 : }
4101 135 : }
4102 :
4103 : /************************************************************************/
4104 : /* WriteVectorLayers() */
4105 : /************************************************************************/
4106 :
4107 188 : void PDS4Dataset::WriteVectorLayers(CPLXMLNode *psProduct)
4108 : {
4109 376 : CPLString osPrefix;
4110 188 : if (STARTS_WITH(psProduct->pszValue, "pds:"))
4111 0 : osPrefix = "pds:";
4112 :
4113 260 : for (auto &poLayer : m_apoLayers)
4114 : {
4115 72 : if (!poLayer->IsDirtyHeader())
4116 3 : continue;
4117 :
4118 69 : if (poLayer->GetFeatureCount(false) == 0)
4119 : {
4120 16 : CPLError(CE_Warning, CPLE_AppDefined,
4121 : "Writing header for layer %s which has 0 features. "
4122 : "This is not legal in PDS4",
4123 16 : poLayer->GetName());
4124 : }
4125 :
4126 69 : if (poLayer->GetRawFieldCount() == 0)
4127 : {
4128 16 : CPLError(CE_Warning, CPLE_AppDefined,
4129 : "Writing header for layer %s which has 0 fields. "
4130 : "This is not legal in PDS4",
4131 16 : poLayer->GetName());
4132 : }
4133 :
4134 : const std::string osRelativePath(
4135 138 : CPLExtractRelativePath(CPLGetPathSafe(m_osXMLFilename).c_str(),
4136 207 : poLayer->GetFileName(), nullptr));
4137 :
4138 69 : bool bFound = false;
4139 634 : for (CPLXMLNode *psIter = psProduct->psChild; psIter != nullptr;
4140 565 : psIter = psIter->psNext)
4141 : {
4142 733 : if (psIter->eType == CXT_Element &&
4143 164 : strcmp(psIter->pszValue,
4144 733 : (osPrefix + "File_Area_Observational").c_str()) == 0)
4145 : {
4146 23 : const char *pszFilename = CPLGetXMLValue(
4147 : psIter,
4148 46 : (osPrefix + "File." + osPrefix + "file_name").c_str(), "");
4149 23 : if (strcmp(pszFilename, osRelativePath.c_str()) == 0)
4150 : {
4151 4 : poLayer->RefreshFileAreaObservational(psIter);
4152 4 : bFound = true;
4153 4 : break;
4154 : }
4155 : }
4156 : }
4157 69 : if (!bFound)
4158 : {
4159 65 : CPLXMLNode *psFAO = CPLCreateXMLNode(
4160 : psProduct, CXT_Element,
4161 130 : (osPrefix + "File_Area_Observational").c_str());
4162 65 : CPLXMLNode *psFile = CPLCreateXMLNode(psFAO, CXT_Element,
4163 130 : (osPrefix + "File").c_str());
4164 130 : CPLCreateXMLElementAndValue(psFile,
4165 130 : (osPrefix + "file_name").c_str(),
4166 : osRelativePath.c_str());
4167 65 : poLayer->RefreshFileAreaObservational(psFAO);
4168 : }
4169 : }
4170 188 : }
4171 :
4172 : /************************************************************************/
4173 : /* CreateHeader() */
4174 : /************************************************************************/
4175 :
4176 181 : void PDS4Dataset::CreateHeader(CPLXMLNode *psProduct,
4177 : const char *pszCARTVersion)
4178 : {
4179 181 : CPLString osPrefix;
4180 181 : if (STARTS_WITH(psProduct->pszValue, "pds:"))
4181 0 : osPrefix = "pds:";
4182 :
4183 181 : if (m_oSRS.IsEmpty() && GetLayerCount() >= 1)
4184 : {
4185 46 : if (const auto poSRS = GetLayer(0)->GetSpatialRef())
4186 2 : m_oSRS = *poSRS;
4187 : }
4188 :
4189 280 : if (!m_oSRS.IsEmpty() &&
4190 99 : CSLFetchNameValue(m_papszCreationOptions, "VAR_TARGET") == nullptr)
4191 : {
4192 98 : const char *pszTarget = nullptr;
4193 98 : if (fabs(m_oSRS.GetSemiMajor() - 6378137) < 0.001 * 6378137)
4194 : {
4195 77 : pszTarget = "Earth";
4196 77 : m_papszCreationOptions = CSLSetNameValue(
4197 : m_papszCreationOptions, "VAR_TARGET_TYPE", "Planet");
4198 : }
4199 : else
4200 : {
4201 21 : const char *pszDatum = m_oSRS.GetAttrValue("DATUM");
4202 21 : if (pszDatum && STARTS_WITH(pszDatum, "D_"))
4203 : {
4204 3 : pszTarget = pszDatum + 2;
4205 : }
4206 18 : else if (pszDatum)
4207 : {
4208 18 : pszTarget = pszDatum;
4209 : }
4210 : }
4211 98 : if (pszTarget)
4212 : {
4213 98 : m_papszCreationOptions = CSLSetNameValue(m_papszCreationOptions,
4214 : "VAR_TARGET", pszTarget);
4215 : }
4216 : }
4217 181 : SubstituteVariables(psProduct, m_papszCreationOptions);
4218 :
4219 : // Remove <Discipline_Area>/<disp:Display_Settings> if there is no raster
4220 181 : if (GetRasterCount() == 0)
4221 : {
4222 : CPLXMLNode *psDisciplineArea =
4223 141 : CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area." +
4224 94 : osPrefix + "Discipline_Area")
4225 : .c_str());
4226 47 : if (psDisciplineArea)
4227 : {
4228 : CPLXMLNode *psDisplaySettings =
4229 47 : CPLGetXMLNode(psDisciplineArea, "disp:Display_Settings");
4230 47 : if (psDisplaySettings)
4231 : {
4232 47 : CPLRemoveXMLChild(psDisciplineArea, psDisplaySettings);
4233 47 : CPLDestroyXMLNode(psDisplaySettings);
4234 : }
4235 : }
4236 : }
4237 :
4238 : // Depending on the version of the DISP schema, Local_Internal_Reference
4239 : // may be in the disp: namespace or the default one.
4240 : const auto GetLocalIdentifierReferenceFromDisciplineArea =
4241 224 : [](const CPLXMLNode *psDisciplineArea, const char *pszDefault)
4242 : {
4243 224 : return CPLGetXMLValue(
4244 : psDisciplineArea,
4245 : "disp:Display_Settings.Local_Internal_Reference."
4246 : "local_identifier_reference",
4247 : CPLGetXMLValue(
4248 : psDisciplineArea,
4249 : "disp:Display_Settings.disp:Local_Internal_Reference."
4250 : "local_identifier_reference",
4251 224 : pszDefault));
4252 : };
4253 :
4254 181 : if (GetRasterCount() || !m_oSRS.IsEmpty())
4255 : {
4256 : CPLXMLNode *psDisciplineArea =
4257 408 : CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area." +
4258 272 : osPrefix + "Discipline_Area")
4259 : .c_str());
4260 136 : if (GetRasterCount() && !(m_bGotTransform && !m_oSRS.IsEmpty()))
4261 : {
4262 : // if we have no georeferencing, strip any existing georeferencing
4263 : // from the template
4264 37 : if (psDisciplineArea)
4265 : {
4266 : CPLXMLNode *psCart =
4267 33 : CPLGetXMLNode(psDisciplineArea, "cart:Cartography");
4268 33 : if (psCart == nullptr)
4269 32 : psCart = CPLGetXMLNode(psDisciplineArea, "Cartography");
4270 33 : if (psCart)
4271 : {
4272 1 : CPLRemoveXMLChild(psDisciplineArea, psCart);
4273 1 : CPLDestroyXMLNode(psCart);
4274 : }
4275 :
4276 33 : if (CPLGetXMLNode(psDisciplineArea,
4277 33 : "sp:Spectral_Characteristics"))
4278 : {
4279 : const char *pszArrayType =
4280 1 : CSLFetchNameValue(m_papszCreationOptions, "ARRAY_TYPE");
4281 : // The schematron PDS4_SP_1100.sch requires that
4282 : // sp:local_identifier_reference is used by
4283 : // Array_[2D|3D]_Spectrum/pds:local_identifier
4284 1 : if (pszArrayType == nullptr)
4285 : {
4286 1 : m_papszCreationOptions =
4287 1 : CSLSetNameValue(m_papszCreationOptions,
4288 : "ARRAY_TYPE", "Array_3D_Spectrum");
4289 : }
4290 0 : else if (!EQUAL(pszArrayType, "Array_2D_Spectrum") &&
4291 0 : !EQUAL(pszArrayType, "Array_3D_Spectrum"))
4292 : {
4293 0 : CPLError(CE_Warning, CPLE_AppDefined,
4294 : "PDS4_SP_xxxx.sch schematron requires the "
4295 : "use of ARRAY_TYPE=Array_2D_Spectrum or "
4296 : "Array_3D_Spectrum");
4297 : }
4298 : }
4299 : }
4300 : }
4301 : else
4302 : {
4303 99 : if (psDisciplineArea == nullptr)
4304 : {
4305 2 : CPLXMLNode *psTI = CPLGetXMLNode(
4306 4 : psProduct, (osPrefix + "Observation_Area." + osPrefix +
4307 : "Target_Identification")
4308 : .c_str());
4309 2 : if (psTI == nullptr)
4310 : {
4311 1 : CPLError(CE_Failure, CPLE_AppDefined,
4312 : "Cannot find Target_Identification element in "
4313 : "template");
4314 1 : return;
4315 : }
4316 : psDisciplineArea =
4317 1 : CPLCreateXMLNode(nullptr, CXT_Element,
4318 2 : (osPrefix + "Discipline_Area").c_str());
4319 1 : if (psTI->psNext)
4320 0 : psDisciplineArea->psNext = psTI->psNext;
4321 1 : psTI->psNext = psDisciplineArea;
4322 : }
4323 : CPLXMLNode *psCart =
4324 98 : CPLGetXMLNode(psDisciplineArea, "cart:Cartography");
4325 98 : if (psCart == nullptr)
4326 93 : psCart = CPLGetXMLNode(psDisciplineArea, "Cartography");
4327 98 : if (psCart == nullptr)
4328 : {
4329 93 : psCart = CPLCreateXMLNode(psDisciplineArea, CXT_Element,
4330 : "cart:Cartography");
4331 93 : if (CPLGetXMLNode(psProduct, "xmlns:cart") == nullptr)
4332 : {
4333 : CPLXMLNode *psNS =
4334 1 : CPLCreateXMLNode(nullptr, CXT_Attribute, "xmlns:cart");
4335 1 : CPLCreateXMLNode(psNS, CXT_Text,
4336 : "http://pds.nasa.gov/pds4/cart/v1");
4337 1 : CPLAddXMLChild(psProduct, psNS);
4338 : CPLXMLNode *psSchemaLoc =
4339 1 : CPLGetXMLNode(psProduct, "xsi:schemaLocation");
4340 1 : if (psSchemaLoc != nullptr &&
4341 1 : psSchemaLoc->psChild != nullptr &&
4342 1 : psSchemaLoc->psChild->pszValue != nullptr)
4343 : {
4344 2 : CPLString osCartSchema;
4345 1 : if (strstr(psSchemaLoc->psChild->pszValue,
4346 : "PDS4_PDS_1800.xsd"))
4347 : {
4348 : // GDAL 2.4
4349 : osCartSchema = "https://pds.nasa.gov/pds4/cart/v1/"
4350 1 : "PDS4_CART_1700.xsd";
4351 1 : pszCARTVersion = "1700";
4352 : }
4353 0 : else if (strstr(psSchemaLoc->psChild->pszValue,
4354 : "PDS4_PDS_1B00.xsd"))
4355 : {
4356 : // GDAL 3.0
4357 : osCartSchema =
4358 : "https://raw.githubusercontent.com/"
4359 : "nasa-pds-data-dictionaries/ldd-cart/master/"
4360 0 : "build/1.B.0.0/PDS4_CART_1B00.xsd";
4361 0 : pszCARTVersion = "1B00";
4362 : }
4363 0 : else if (strstr(psSchemaLoc->psChild->pszValue,
4364 : "PDS4_PDS_1D00.xsd"))
4365 : {
4366 : // GDAL 3.1
4367 : osCartSchema = "https://pds.nasa.gov/pds4/cart/v1/"
4368 0 : "PDS4_CART_1D00_1933.xsd";
4369 0 : pszCARTVersion = "1D00_1933";
4370 : }
4371 0 : else if (strstr(psSchemaLoc->psChild->pszValue,
4372 : "PDS4_PDS_1G00_1950.xsd"))
4373 : {
4374 : // GDAL 3.4
4375 : osCartSchema = "https://pds.nasa.gov/pds4/cart/v1/"
4376 0 : "PDS4_CART_1G00_1950.xsd";
4377 0 : pszCARTVersion = "1G00_1950";
4378 : }
4379 : else
4380 : {
4381 : // GDAL 3.12
4382 : osCartSchema =
4383 : "https://pds.nasa.gov/pds4/cart/v1/"
4384 0 : "PDS4_CART_" CURRENT_CART_VERSION ".xsd";
4385 0 : pszCARTVersion = CURRENT_CART_VERSION;
4386 : }
4387 1 : CPLString osNewVal(psSchemaLoc->psChild->pszValue);
4388 : osNewVal +=
4389 1 : " http://pds.nasa.gov/pds4/cart/v1 " + osCartSchema;
4390 1 : CPLFree(psSchemaLoc->psChild->pszValue);
4391 1 : psSchemaLoc->psChild->pszValue = CPLStrdup(osNewVal);
4392 : }
4393 : }
4394 : }
4395 : else
4396 : {
4397 5 : if (psCart->psChild)
4398 : {
4399 5 : CPLDestroyXMLNode(psCart->psChild);
4400 5 : psCart->psChild = nullptr;
4401 : }
4402 : }
4403 :
4404 98 : if (IsCARTVersionGTE(pszCARTVersion, "1900"))
4405 : {
4406 : const char *pszLocalIdentifier =
4407 93 : GetLocalIdentifierReferenceFromDisciplineArea(
4408 : psDisciplineArea,
4409 93 : GetRasterCount() == 0 && GetLayerCount() > 0
4410 2 : ? GetLayer(0)->GetName()
4411 : : "image");
4412 93 : CPLXMLNode *psLIR = CPLCreateXMLNode(
4413 : psCart, CXT_Element,
4414 186 : (osPrefix + "Local_Internal_Reference").c_str());
4415 93 : CPLCreateXMLElementAndValue(
4416 186 : psLIR, (osPrefix + "local_identifier_reference").c_str(),
4417 : pszLocalIdentifier);
4418 93 : CPLCreateXMLElementAndValue(
4419 186 : psLIR, (osPrefix + "local_reference_type").c_str(),
4420 : "cartography_parameters_to_image_object");
4421 : }
4422 :
4423 98 : WriteGeoreferencing(psCart, pszCARTVersion);
4424 : }
4425 :
4426 270 : const char *pszVertDir = CSLFetchNameValue(
4427 135 : m_papszCreationOptions, "VAR_VERTICAL_DISPLAY_DIRECTION");
4428 135 : if (pszVertDir)
4429 : {
4430 : CPLXMLNode *psVertDirNode =
4431 1 : CPLGetXMLNode(psDisciplineArea,
4432 : "disp:Display_Settings.disp:Display_Direction."
4433 : "disp:vertical_display_direction");
4434 1 : if (psVertDirNode == nullptr)
4435 : {
4436 0 : CPLError(
4437 : CE_Warning, CPLE_AppDefined,
4438 : "PDS4 template lacks a disp:vertical_display_direction "
4439 : "element where to write %s",
4440 : pszVertDir);
4441 : }
4442 : else
4443 : {
4444 1 : CPLDestroyXMLNode(psVertDirNode->psChild);
4445 1 : psVertDirNode->psChild =
4446 1 : CPLCreateXMLNode(nullptr, CXT_Text, pszVertDir);
4447 : }
4448 : }
4449 : }
4450 : else
4451 : {
4452 : // Remove Observation_Area.Discipline_Area if it contains only
4453 : // <disp:Display_Settings> or is empty
4454 : CPLXMLNode *psObservationArea =
4455 45 : CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area").c_str());
4456 45 : if (psObservationArea)
4457 : {
4458 45 : CPLXMLNode *psDisciplineArea = CPLGetXMLNode(
4459 90 : psObservationArea, (osPrefix + "Discipline_Area").c_str());
4460 45 : if (psDisciplineArea &&
4461 45 : (psDisciplineArea->psChild == nullptr ||
4462 0 : (psDisciplineArea->psChild->eType == CXT_Element &&
4463 0 : psDisciplineArea->psChild->psNext == nullptr &&
4464 0 : strcmp(psDisciplineArea->psChild->pszValue,
4465 : "disp:Display_Settings") == 0)))
4466 : {
4467 45 : CPLRemoveXMLChild(psObservationArea, psDisciplineArea);
4468 45 : CPLDestroyXMLNode(psDisciplineArea);
4469 : }
4470 : }
4471 : }
4472 :
4473 180 : if (m_bStripFileAreaObservationalFromTemplate)
4474 : {
4475 180 : m_bStripFileAreaObservationalFromTemplate = false;
4476 180 : CPLXMLNode *psObservationArea = nullptr;
4477 180 : CPLXMLNode *psPrev = nullptr;
4478 180 : CPLXMLNode *psTemplateSpecialConstants = nullptr;
4479 1618 : for (CPLXMLNode *psIter = psProduct->psChild; psIter != nullptr;)
4480 : {
4481 1809 : if (psIter->eType == CXT_Element &&
4482 1809 : psIter->pszValue == osPrefix + "Observation_Area")
4483 : {
4484 179 : psObservationArea = psIter;
4485 179 : psPrev = psIter;
4486 179 : psIter = psIter->psNext;
4487 : }
4488 1631 : else if (psIter->eType == CXT_Element &&
4489 192 : (psIter->pszValue ==
4490 1631 : osPrefix + "File_Area_Observational" ||
4491 180 : psIter->pszValue ==
4492 1439 : osPrefix + "File_Area_Observational_Supplemental"))
4493 : {
4494 12 : if (psIter->pszValue == osPrefix + "File_Area_Observational")
4495 : {
4496 : psTemplateSpecialConstants =
4497 12 : GetSpecialConstants(osPrefix, psIter);
4498 : }
4499 12 : if (psPrev)
4500 12 : psPrev->psNext = psIter->psNext;
4501 : else
4502 : {
4503 0 : CPLAssert(psProduct->psChild == psIter);
4504 0 : psProduct->psChild = psIter->psNext;
4505 : }
4506 12 : CPLXMLNode *psNext = psIter->psNext;
4507 12 : psIter->psNext = nullptr;
4508 12 : CPLDestroyXMLNode(psIter);
4509 12 : psIter = psNext;
4510 : }
4511 : else
4512 : {
4513 1247 : psPrev = psIter;
4514 1247 : psIter = psIter->psNext;
4515 : }
4516 : }
4517 180 : if (psObservationArea == nullptr)
4518 : {
4519 1 : CPLError(CE_Failure, CPLE_AppDefined,
4520 : "Cannot find Observation_Area in template");
4521 1 : CPLDestroyXMLNode(psTemplateSpecialConstants);
4522 1 : return;
4523 : }
4524 :
4525 179 : if (GetRasterCount())
4526 : {
4527 132 : CPLXMLNode *psFAOPrev = psObservationArea;
4528 134 : while (psFAOPrev->psNext != nullptr &&
4529 4 : psFAOPrev->psNext->eType == CXT_Comment)
4530 : {
4531 2 : psFAOPrev = psFAOPrev->psNext;
4532 : }
4533 132 : if (psFAOPrev->psNext != nullptr)
4534 : {
4535 : // There may be an optional Reference_List element between
4536 : // Observation_Area and File_Area_Observational
4537 4 : if (!(psFAOPrev->psNext->eType == CXT_Element &&
4538 2 : psFAOPrev->psNext->pszValue ==
4539 4 : osPrefix + "Reference_List"))
4540 : {
4541 1 : CPLError(CE_Failure, CPLE_AppDefined,
4542 : "Unexpected content found after Observation_Area "
4543 : "in template");
4544 1 : CPLDestroyXMLNode(psTemplateSpecialConstants);
4545 1 : return;
4546 : }
4547 1 : psFAOPrev = psFAOPrev->psNext;
4548 2 : while (psFAOPrev->psNext != nullptr &&
4549 1 : psFAOPrev->psNext->eType == CXT_Comment)
4550 : {
4551 1 : psFAOPrev = psFAOPrev->psNext;
4552 : }
4553 1 : if (psFAOPrev->psNext != nullptr)
4554 : {
4555 0 : CPLError(CE_Failure, CPLE_AppDefined,
4556 : "Unexpected content found after Reference_List in "
4557 : "template");
4558 0 : CPLDestroyXMLNode(psTemplateSpecialConstants);
4559 0 : return;
4560 : }
4561 : }
4562 :
4563 131 : CPLXMLNode *psFAO = CPLCreateXMLNode(
4564 : nullptr, CXT_Element,
4565 262 : (osPrefix + "File_Area_Observational").c_str());
4566 131 : psFAOPrev->psNext = psFAO;
4567 :
4568 131 : CPLXMLNode *psFile = CPLCreateXMLNode(psFAO, CXT_Element,
4569 262 : (osPrefix + "File").c_str());
4570 262 : CPLCreateXMLElementAndValue(psFile,
4571 262 : (osPrefix + "file_name").c_str(),
4572 : CPLGetFilename(m_osImageFilename));
4573 131 : if (m_bCreatedFromExistingBinaryFile)
4574 : {
4575 7 : CPLCreateXMLNode(psFile, CXT_Comment, PREEXISTING_BINARY_FILE);
4576 : }
4577 : CPLXMLNode *psDisciplineArea =
4578 393 : CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area." +
4579 262 : osPrefix + "Discipline_Area")
4580 : .c_str());
4581 : const char *pszLocalIdentifier =
4582 131 : GetLocalIdentifierReferenceFromDisciplineArea(psDisciplineArea,
4583 : "image");
4584 :
4585 147 : if (m_poExternalDS && m_poExternalDS->GetDriver() &&
4586 16 : EQUAL(m_poExternalDS->GetDriver()->GetDescription(), "GTiff"))
4587 : {
4588 : VSILFILE *fpTemp =
4589 16 : VSIFOpenL(m_poExternalDS->GetDescription(), "rb");
4590 16 : if (fpTemp)
4591 : {
4592 16 : GByte abySignature[4] = {0};
4593 16 : VSIFReadL(abySignature, 1, 4, fpTemp);
4594 16 : VSIFCloseL(fpTemp);
4595 16 : const bool bBigTIFF =
4596 16 : abySignature[2] == 43 || abySignature[3] == 43;
4597 : m_osHeaderParsingStandard =
4598 16 : bBigTIFF ? BIGTIFF_GEOTIFF_STRING : TIFF_GEOTIFF_STRING;
4599 : const char *pszOffset =
4600 16 : m_poExternalDS->GetRasterBand(1)->GetMetadataItem(
4601 16 : "BLOCK_OFFSET_0_0", "TIFF");
4602 16 : if (pszOffset)
4603 16 : m_nBaseOffset = CPLAtoGIntBig(pszOffset);
4604 : }
4605 : }
4606 :
4607 131 : if (!m_osHeaderParsingStandard.empty() && m_nBaseOffset > 0)
4608 : {
4609 22 : CPLXMLNode *psHeader = CPLCreateXMLNode(
4610 44 : psFAO, CXT_Element, (osPrefix + "Header").c_str());
4611 22 : CPLAddXMLAttributeAndValue(
4612 : CPLCreateXMLElementAndValue(
4613 44 : psHeader, (osPrefix + "offset").c_str(), "0"),
4614 : "unit", "byte");
4615 22 : CPLAddXMLAttributeAndValue(
4616 : CPLCreateXMLElementAndValue(
4617 44 : psHeader, (osPrefix + "object_length").c_str(),
4618 : CPLSPrintf(CPL_FRMT_GUIB,
4619 22 : static_cast<GUIntBig>(m_nBaseOffset))),
4620 : "unit", "byte");
4621 44 : CPLCreateXMLElementAndValue(
4622 44 : psHeader, (osPrefix + "parsing_standard_id").c_str(),
4623 : m_osHeaderParsingStandard.c_str());
4624 22 : if (m_osHeaderParsingStandard == TIFF_GEOTIFF_STRING)
4625 : {
4626 18 : CPLCreateXMLElementAndValue(
4627 36 : psHeader, (osPrefix + "description").c_str(),
4628 : "TIFF/GeoTIFF header. The TIFF/GeoTIFF format is used "
4629 : "throughout the geospatial and science communities "
4630 : "to share geographic image data. ");
4631 : }
4632 4 : else if (m_osHeaderParsingStandard == BIGTIFF_GEOTIFF_STRING)
4633 : {
4634 0 : CPLCreateXMLElementAndValue(
4635 0 : psHeader, (osPrefix + "description").c_str(),
4636 : "BigTIFF/GeoTIFF header. The BigTIFF/GeoTIFF format is "
4637 : "used "
4638 : "throughout the geospatial and science communities "
4639 : "to share geographic image data. ");
4640 : }
4641 : }
4642 :
4643 131 : WriteArray(osPrefix, psFAO, pszLocalIdentifier,
4644 : psTemplateSpecialConstants);
4645 : }
4646 : }
4647 : }
4648 :
4649 : /************************************************************************/
4650 : /* WriteHeader() */
4651 : /************************************************************************/
4652 :
4653 193 : void PDS4Dataset::WriteHeader()
4654 : {
4655 : const bool bAppend =
4656 193 : CPLFetchBool(m_papszCreationOptions, "APPEND_SUBDATASET", false);
4657 193 : if (bAppend)
4658 : {
4659 4 : WriteHeaderAppendCase();
4660 5 : return;
4661 : }
4662 :
4663 : CPLXMLNode *psRoot;
4664 189 : if (m_bCreateHeader)
4665 : {
4666 : CPLString osTemplateFilename =
4667 182 : CSLFetchNameValueDef(m_papszCreationOptions, "TEMPLATE", "");
4668 182 : if (!osTemplateFilename.empty())
4669 : {
4670 20 : if (STARTS_WITH(osTemplateFilename, "http://") ||
4671 10 : STARTS_WITH(osTemplateFilename, "https://"))
4672 : {
4673 0 : osTemplateFilename = "/vsicurl_streaming/" + osTemplateFilename;
4674 : }
4675 10 : psRoot = CPLParseXMLFile(osTemplateFilename);
4676 : }
4677 172 : else if (!m_osXMLPDS4.empty())
4678 6 : psRoot = CPLParseXMLString(m_osXMLPDS4);
4679 : else
4680 : {
4681 : #ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES
4682 : #ifdef EMBED_RESOURCE_FILES
4683 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
4684 : #endif
4685 : const char *pszDefaultTemplateFilename =
4686 166 : CPLFindFile("gdal", "pds4_template.xml");
4687 166 : if (pszDefaultTemplateFilename)
4688 : {
4689 166 : psRoot = CPLParseXMLFile(pszDefaultTemplateFilename);
4690 : }
4691 : else
4692 : #endif
4693 : {
4694 : #ifdef EMBED_RESOURCE_FILES
4695 : static const bool bOnce [[maybe_unused]] = []()
4696 : {
4697 : CPLDebug("PDS4", "Using embedded pds4_template.xml");
4698 : return true;
4699 : }();
4700 : psRoot = CPLParseXMLString(PDS4GetEmbeddedTemplate());
4701 : #else
4702 0 : CPLError(CE_Failure, CPLE_AppDefined,
4703 : "Cannot find pds4_template.xml and TEMPLATE "
4704 : "creation option not specified");
4705 0 : return;
4706 : #endif
4707 : }
4708 : }
4709 : }
4710 : else
4711 : {
4712 7 : psRoot = CPLParseXMLFile(m_osXMLFilename);
4713 : }
4714 189 : CPLXMLTreeCloser oCloser(psRoot);
4715 189 : psRoot = oCloser.get();
4716 189 : if (psRoot == nullptr)
4717 0 : return;
4718 189 : CPLXMLNode *psProduct = CPLGetXMLNode(psRoot, "=Product_Observational");
4719 189 : if (psProduct == nullptr)
4720 : {
4721 1 : psProduct = CPLGetXMLNode(psRoot, "=pds:Product_Observational");
4722 : }
4723 189 : if (psProduct == nullptr)
4724 : {
4725 1 : CPLError(CE_Failure, CPLE_AppDefined,
4726 : "Cannot find Product_Observational element in template");
4727 1 : return;
4728 : }
4729 :
4730 188 : if (m_bCreateHeader)
4731 : {
4732 362 : CPLString osCARTVersion(CURRENT_CART_VERSION);
4733 181 : char *pszXML = CPLSerializeXMLTree(psRoot);
4734 181 : if (pszXML)
4735 : {
4736 181 : const char *pszIter = pszXML;
4737 : while (true)
4738 : {
4739 355 : const char *pszCartSchema = strstr(pszIter, "PDS4_CART_");
4740 355 : if (pszCartSchema)
4741 : {
4742 348 : const char *pszXSDExtension = strstr(pszCartSchema, ".xsd");
4743 348 : if (pszXSDExtension &&
4744 348 : pszXSDExtension - pszCartSchema <= 20)
4745 : {
4746 174 : osCARTVersion = pszCartSchema + strlen("PDS4_CART_");
4747 174 : osCARTVersion.resize(pszXSDExtension - pszCartSchema -
4748 : strlen("PDS4_CART_"));
4749 174 : break;
4750 : }
4751 : else
4752 : {
4753 174 : pszIter = pszCartSchema + 1;
4754 : }
4755 : }
4756 : else
4757 : {
4758 7 : break;
4759 : }
4760 174 : }
4761 :
4762 181 : CPLFree(pszXML);
4763 : }
4764 :
4765 181 : CreateHeader(psProduct, osCARTVersion.c_str());
4766 : }
4767 :
4768 188 : WriteVectorLayers(psProduct);
4769 :
4770 188 : CPLSerializeXMLTreeToFile(psRoot, GetDescription());
4771 : }
4772 :
4773 : /************************************************************************/
4774 : /* ICreateLayer() */
4775 : /************************************************************************/
4776 :
4777 66 : OGRLayer *PDS4Dataset::ICreateLayer(const char *pszName,
4778 : const OGRGeomFieldDefn *poGeomFieldDefn,
4779 : CSLConstList papszOptions)
4780 : {
4781 : const char *pszTableType =
4782 66 : CSLFetchNameValueDef(papszOptions, "TABLE_TYPE", "DELIMITED");
4783 66 : if (!EQUAL(pszTableType, "CHARACTER") && !EQUAL(pszTableType, "BINARY") &&
4784 55 : !EQUAL(pszTableType, "DELIMITED"))
4785 : {
4786 0 : return nullptr;
4787 : }
4788 :
4789 66 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
4790 : const auto poSpatialRef =
4791 66 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
4792 :
4793 126 : const char *pszExt = EQUAL(pszTableType, "CHARACTER") ? "dat"
4794 60 : : EQUAL(pszTableType, "BINARY") ? "bin"
4795 : : "csv";
4796 132 : std::string osBasename(pszName);
4797 629 : for (char &ch : osBasename)
4798 : {
4799 563 : if (!isalnum(static_cast<unsigned char>(ch)) &&
4800 53 : static_cast<unsigned>(ch) <= 127)
4801 53 : ch = '_';
4802 : }
4803 :
4804 198 : CPLString osFullFilename(CPLFormFilenameSafe(
4805 132 : CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(),
4806 198 : CPLGetBasenameSafe(m_osXMLFilename.c_str()).c_str(), nullptr));
4807 66 : osFullFilename += '_';
4808 66 : osFullFilename += osBasename.c_str();
4809 66 : osFullFilename += '.';
4810 66 : osFullFilename += pszExt;
4811 : VSIStatBufL sStat;
4812 66 : if (VSIStatL(osFullFilename, &sStat) == 0)
4813 : {
4814 0 : CPLError(CE_Failure, CPLE_AppDefined,
4815 : "%s already exists. Please delete it before, or "
4816 : "rename the layer",
4817 : osFullFilename.c_str());
4818 0 : return nullptr;
4819 : }
4820 :
4821 66 : if (EQUAL(pszTableType, "DELIMITED"))
4822 : {
4823 : auto poLayer = std::make_unique<PDS4DelimitedTable>(
4824 55 : this, pszName, osFullFilename, true);
4825 55 : if (!poLayer->InitializeNewLayer(poSpatialRef, false, eGType,
4826 : papszOptions))
4827 : {
4828 1 : return nullptr;
4829 : }
4830 54 : m_apoLayers.push_back(
4831 108 : std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
4832 : }
4833 : else
4834 : {
4835 0 : std::unique_ptr<PDS4FixedWidthTable> poLayer;
4836 11 : if (EQUAL(pszTableType, "CHARACTER"))
4837 6 : poLayer = std::make_unique<PDS4TableCharacter>(
4838 12 : this, pszName, osFullFilename, true);
4839 : else
4840 5 : poLayer = std::make_unique<PDS4TableBinary>(this, pszName,
4841 10 : osFullFilename, true);
4842 11 : if (!poLayer->InitializeNewLayer(poSpatialRef, false, eGType,
4843 : papszOptions))
4844 : {
4845 0 : return nullptr;
4846 : }
4847 11 : m_apoLayers.push_back(
4848 22 : std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
4849 : }
4850 65 : return dynamic_cast<OGRLayer *>(m_apoLayers.back().get());
4851 : }
4852 :
4853 : /************************************************************************/
4854 : /* TestCapability() */
4855 : /************************************************************************/
4856 :
4857 69 : bool PDS4Dataset::TestCapability(const char *pszCap) const
4858 : {
4859 69 : if (EQUAL(pszCap, ODsCCreateLayer))
4860 35 : return eAccess == GA_Update;
4861 34 : else if (EQUAL(pszCap, ODsCZGeometries))
4862 6 : return TRUE;
4863 : else
4864 28 : return FALSE;
4865 : }
4866 :
4867 : /************************************************************************/
4868 : /* Create() */
4869 : /************************************************************************/
4870 :
4871 140 : GDALDataset *PDS4Dataset::Create(const char *pszFilename, int nXSize,
4872 : int nYSize, int nBandsIn, GDALDataType eType,
4873 : CSLConstList papszOptions)
4874 : {
4875 280 : return CreateInternal(pszFilename, nullptr, nXSize, nYSize, nBandsIn, eType,
4876 : papszOptions)
4877 140 : .release();
4878 : }
4879 :
4880 : /************************************************************************/
4881 : /* CreateInternal() */
4882 : /************************************************************************/
4883 :
4884 200 : std::unique_ptr<PDS4Dataset> PDS4Dataset::CreateInternal(
4885 : const char *pszFilename, GDALDataset *poSrcDS, int nXSize, int nYSize,
4886 : int nBandsIn, GDALDataType eType, const char *const *papszOptionsIn)
4887 : {
4888 400 : CPLStringList aosOptions(papszOptionsIn);
4889 :
4890 200 : if (nXSize == 0 && nYSize == 0 && nBandsIn == 0 && eType == GDT_Unknown)
4891 : {
4892 : // Vector file creation
4893 94 : auto poDS = std::make_unique<PDS4Dataset>();
4894 47 : poDS->SetDescription(pszFilename);
4895 47 : poDS->nRasterXSize = 0;
4896 47 : poDS->nRasterYSize = 0;
4897 47 : poDS->eAccess = GA_Update;
4898 47 : poDS->m_osXMLFilename = pszFilename;
4899 47 : poDS->m_bCreateHeader = true;
4900 47 : poDS->m_bStripFileAreaObservationalFromTemplate = true;
4901 47 : poDS->m_papszCreationOptions = CSLDuplicate(aosOptions.List());
4902 47 : poDS->m_bUseSrcLabel = aosOptions.FetchBool("USE_SRC_LABEL", true);
4903 47 : return poDS;
4904 : }
4905 :
4906 153 : if (nXSize == 0)
4907 0 : return nullptr;
4908 :
4909 153 : if (!(eType == GDT_UInt8 || eType == GDT_Int8 || eType == GDT_Int16 ||
4910 50 : eType == GDT_UInt16 || eType == GDT_Int32 || eType == GDT_UInt32 ||
4911 35 : eType == GDT_Int64 || eType == GDT_UInt64 || eType == GDT_Float32 ||
4912 20 : eType == GDT_Float64 || eType == GDT_CFloat32 ||
4913 10 : eType == GDT_CFloat64))
4914 : {
4915 6 : CPLError(
4916 : CE_Failure, CPLE_NotSupported,
4917 : "The PDS4 driver does not supporting creating files of type %s.",
4918 : GDALGetDataTypeName(eType));
4919 6 : return nullptr;
4920 : }
4921 :
4922 147 : if (nBandsIn == 0)
4923 : {
4924 1 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid number of bands");
4925 1 : return nullptr;
4926 : }
4927 :
4928 : const char *pszArrayType =
4929 146 : aosOptions.FetchNameValueDef("ARRAY_TYPE", "Array_3D_Image");
4930 146 : const bool bIsArray2D = STARTS_WITH(pszArrayType, "Array_2D");
4931 146 : if (nBandsIn > 1 && bIsArray2D)
4932 : {
4933 1 : CPLError(CE_Failure, CPLE_NotSupported,
4934 : "ARRAY_TYPE=%s is not supported for a multi-band raster",
4935 : pszArrayType);
4936 1 : return nullptr;
4937 : }
4938 :
4939 : /* -------------------------------------------------------------------- */
4940 : /* Compute pixel, line and band offsets */
4941 : /* -------------------------------------------------------------------- */
4942 145 : const int nItemSize = GDALGetDataTypeSizeBytes(eType);
4943 : int nLineOffset, nPixelOffset;
4944 : vsi_l_offset nBandOffset;
4945 :
4946 : const char *pszInterleave =
4947 145 : aosOptions.FetchNameValueDef(GDALMD_INTERLEAVE, "BSQ");
4948 145 : if (bIsArray2D)
4949 1 : pszInterleave = "BIP";
4950 :
4951 145 : if (EQUAL(pszInterleave, "BIP"))
4952 : {
4953 4 : nPixelOffset = nItemSize * nBandsIn;
4954 4 : if (nPixelOffset > INT_MAX / nBandsIn)
4955 : {
4956 0 : return nullptr;
4957 : }
4958 4 : nLineOffset = nPixelOffset * nXSize;
4959 4 : nBandOffset = nItemSize;
4960 : }
4961 141 : else if (EQUAL(pszInterleave, "BSQ"))
4962 : {
4963 137 : nPixelOffset = nItemSize;
4964 137 : if (nPixelOffset > INT_MAX / nXSize)
4965 : {
4966 0 : return nullptr;
4967 : }
4968 137 : nLineOffset = nPixelOffset * nXSize;
4969 137 : nBandOffset = static_cast<vsi_l_offset>(nLineOffset) * nYSize;
4970 : }
4971 4 : else if (EQUAL(pszInterleave, "BIL"))
4972 : {
4973 3 : nPixelOffset = nItemSize;
4974 3 : if (nPixelOffset > INT_MAX / nBandsIn ||
4975 3 : nPixelOffset * nBandsIn > INT_MAX / nXSize)
4976 : {
4977 0 : return nullptr;
4978 : }
4979 3 : nLineOffset = nItemSize * nBandsIn * nXSize;
4980 3 : nBandOffset = static_cast<vsi_l_offset>(nItemSize) * nXSize;
4981 : }
4982 : else
4983 : {
4984 1 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid value for INTERLEAVE");
4985 1 : return nullptr;
4986 : }
4987 :
4988 : const char *pszImageFormat =
4989 144 : aosOptions.FetchNameValueDef("IMAGE_FORMAT", "RAW");
4990 144 : const char *pszImageExtension = aosOptions.FetchNameValueDef(
4991 144 : "IMAGE_EXTENSION", EQUAL(pszImageFormat, "RAW") ? "img" : "tif");
4992 : CPLString osImageFilename(aosOptions.FetchNameValueDef(
4993 : "IMAGE_FILENAME",
4994 288 : CPLResetExtensionSafe(pszFilename, pszImageExtension).c_str()));
4995 :
4996 144 : const bool bAppend = aosOptions.FetchBool("APPEND_SUBDATASET", false);
4997 144 : if (bAppend)
4998 : {
4999 4 : GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
5000 4 : auto poExistingPDS4 = OpenInternal(&oOpenInfo);
5001 4 : if (!poExistingPDS4)
5002 : {
5003 0 : return nullptr;
5004 : }
5005 4 : osImageFilename = poExistingPDS4->m_osImageFilename;
5006 4 : poExistingPDS4.reset();
5007 :
5008 : auto poImageDS = std::unique_ptr<GDALDataset>(
5009 8 : GDALDataset::Open(osImageFilename, GDAL_OF_RASTER));
5010 6 : if (poImageDS && poImageDS->GetDriver() &&
5011 2 : EQUAL(poImageDS->GetDriver()->GetDescription(), "GTiff"))
5012 : {
5013 2 : pszImageFormat = "GEOTIFF";
5014 : }
5015 : }
5016 :
5017 144 : GDALDataset *poExternalDS = nullptr;
5018 144 : VSILFILE *fpImage = nullptr;
5019 144 : vsi_l_offset nBaseOffset = 0;
5020 144 : bool bIsLSB = true;
5021 288 : CPLString osHeaderParsingStandard;
5022 : const bool bCreateLabelOnly =
5023 144 : aosOptions.FetchBool("CREATE_LABEL_ONLY", false);
5024 144 : if (bCreateLabelOnly)
5025 : {
5026 8 : if (poSrcDS == nullptr)
5027 : {
5028 0 : CPLError(
5029 : CE_Failure, CPLE_AppDefined,
5030 : "CREATE_LABEL_ONLY is only compatible with CreateCopy() mode");
5031 1 : return nullptr;
5032 : }
5033 8 : RawBinaryLayout sLayout;
5034 8 : if (!poSrcDS->GetRawBinaryLayout(sLayout))
5035 : {
5036 1 : CPLError(CE_Failure, CPLE_AppDefined,
5037 : "Source dataset is not compatible with raw binary format");
5038 1 : return nullptr;
5039 : }
5040 7 : if ((nBandsIn > 1 &&
5041 7 : sLayout.eInterleaving == RawBinaryLayout::Interleaving::UNKNOWN) ||
5042 6 : (nBandsIn == 1 &&
5043 6 : !(sLayout.nPixelOffset == nItemSize &&
5044 6 : sLayout.nLineOffset == sLayout.nPixelOffset * nXSize)))
5045 : {
5046 0 : CPLError(CE_Failure, CPLE_AppDefined,
5047 : "Source dataset has an interleaving not handled in PDS4");
5048 0 : return nullptr;
5049 : }
5050 7 : fpImage = VSIFOpenL(sLayout.osRawFilename.c_str(), "rb");
5051 7 : if (fpImage == nullptr)
5052 : {
5053 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open raw image %s",
5054 : sLayout.osRawFilename.c_str());
5055 0 : return nullptr;
5056 : }
5057 7 : osImageFilename = sLayout.osRawFilename;
5058 7 : if (nBandsIn == 1 ||
5059 1 : sLayout.eInterleaving == RawBinaryLayout::Interleaving::BIP)
5060 7 : pszInterleave = "BIP";
5061 0 : else if (sLayout.eInterleaving == RawBinaryLayout::Interleaving::BIL)
5062 0 : pszInterleave = "BIL";
5063 : else
5064 0 : pszInterleave = "BSQ";
5065 7 : nBaseOffset = sLayout.nImageOffset;
5066 7 : nPixelOffset = static_cast<int>(sLayout.nPixelOffset);
5067 7 : nLineOffset = static_cast<int>(sLayout.nLineOffset);
5068 7 : nBandOffset = static_cast<vsi_l_offset>(sLayout.nBandOffset);
5069 7 : bIsLSB = sLayout.bLittleEndianOrder;
5070 7 : auto poSrcDriver = poSrcDS->GetDriver();
5071 7 : if (poSrcDriver)
5072 : {
5073 7 : auto pszDriverName = poSrcDriver->GetDescription();
5074 7 : if (EQUAL(pszDriverName, "GTiff"))
5075 : {
5076 2 : GByte abySignature[4] = {0};
5077 2 : VSIFReadL(abySignature, 1, 4, fpImage);
5078 2 : const bool bBigTIFF =
5079 2 : abySignature[2] == 43 || abySignature[3] == 43;
5080 : osHeaderParsingStandard =
5081 2 : bBigTIFF ? BIGTIFF_GEOTIFF_STRING : TIFF_GEOTIFF_STRING;
5082 : }
5083 5 : else if (EQUAL(pszDriverName, "ISIS3"))
5084 : {
5085 1 : osHeaderParsingStandard = "ISIS3";
5086 : }
5087 4 : else if (EQUAL(pszDriverName, "VICAR"))
5088 : {
5089 1 : osHeaderParsingStandard = "VICAR2";
5090 : }
5091 3 : else if (EQUAL(pszDriverName, "PDS"))
5092 : {
5093 1 : osHeaderParsingStandard = "PDS3";
5094 : }
5095 2 : else if (EQUAL(pszDriverName, "FITS"))
5096 : {
5097 1 : osHeaderParsingStandard = "FITS 3.0";
5098 : aosOptions.SetNameValue("VAR_VERTICAL_DISPLAY_DIRECTION",
5099 1 : "Bottom to Top");
5100 : }
5101 : }
5102 : }
5103 136 : else if (EQUAL(pszImageFormat, "GEOTIFF"))
5104 : {
5105 20 : if (EQUAL(pszInterleave, "BIL"))
5106 : {
5107 2 : if (aosOptions.FetchBool("@INTERLEAVE_ADDED_AUTOMATICALLY", false))
5108 : {
5109 1 : pszInterleave = "BSQ";
5110 : }
5111 : else
5112 : {
5113 1 : CPLError(CE_Failure, CPLE_AppDefined,
5114 : "INTERLEAVE=BIL not supported for GeoTIFF in PDS4");
5115 1 : return nullptr;
5116 : }
5117 : }
5118 : GDALDriver *poDrv =
5119 19 : static_cast<GDALDriver *>(GDALGetDriverByName("GTiff"));
5120 19 : if (poDrv == nullptr)
5121 : {
5122 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find GTiff driver");
5123 0 : return nullptr;
5124 : }
5125 19 : char **papszGTiffOptions = nullptr;
5126 : #ifdef notdef
5127 : // In practice I can't see which option we can really use
5128 : const char *pszGTiffOptions =
5129 : CSLFetchNameValueDef(papszOptions, "GEOTIFF_OPTIONS", "");
5130 : char **papszTokens = CSLTokenizeString2(pszGTiffOptions, ",", 0);
5131 : if (CPLFetchBool(papszTokens, "TILED", false))
5132 : {
5133 : CSLDestroy(papszTokens);
5134 : CPLError(CE_Failure, CPLE_AppDefined,
5135 : "Tiled GeoTIFF is not supported for PDS4");
5136 : return NULL;
5137 : }
5138 : if (!EQUAL(CSLFetchNameValueDef(papszTokens, "COMPRESS", "NONE"),
5139 : "NONE"))
5140 : {
5141 : CSLDestroy(papszTokens);
5142 : CPLError(CE_Failure, CPLE_AppDefined,
5143 : "Compressed GeoTIFF is not supported for PDS4");
5144 : return NULL;
5145 : }
5146 : papszGTiffOptions =
5147 : CSLSetNameValue(papszGTiffOptions, "ENDIANNESS", "LITTLE");
5148 : for (int i = 0; papszTokens[i] != NULL; i++)
5149 : {
5150 : papszGTiffOptions = CSLAddString(papszGTiffOptions, papszTokens[i]);
5151 : }
5152 : CSLDestroy(papszTokens);
5153 : #endif
5154 :
5155 : papszGTiffOptions =
5156 19 : CSLSetNameValue(papszGTiffOptions, GDALMD_INTERLEAVE,
5157 19 : EQUAL(pszInterleave, "BSQ") ? "BAND" : "PIXEL");
5158 : // Will make sure that our blocks at nodata are not optimized
5159 : // away but indeed well written
5160 19 : papszGTiffOptions = CSLSetNameValue(
5161 : papszGTiffOptions, "@WRITE_EMPTY_TILES_SYNCHRONOUSLY", "YES");
5162 19 : if (nBandsIn > 1 && EQUAL(pszInterleave, "BSQ"))
5163 : {
5164 : papszGTiffOptions =
5165 2 : CSLSetNameValue(papszGTiffOptions, "BLOCKYSIZE", "1");
5166 : }
5167 :
5168 19 : if (bAppend)
5169 : {
5170 : papszGTiffOptions =
5171 2 : CSLAddString(papszGTiffOptions, "APPEND_SUBDATASET=YES");
5172 : }
5173 :
5174 19 : poExternalDS = poDrv->Create(osImageFilename, nXSize, nYSize, nBandsIn,
5175 : eType, papszGTiffOptions);
5176 19 : CSLDestroy(papszGTiffOptions);
5177 19 : if (poExternalDS == nullptr)
5178 : {
5179 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s",
5180 : osImageFilename.c_str());
5181 1 : return nullptr;
5182 : }
5183 : }
5184 : else
5185 : {
5186 230 : fpImage = VSIFOpenL(
5187 : osImageFilename,
5188 : bAppend ? "rb+"
5189 114 : : VSISupportsRandomWrite(osImageFilename.c_str(), true) ? "wb+"
5190 : : "wb");
5191 116 : if (fpImage == nullptr)
5192 : {
5193 2 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s",
5194 : osImageFilename.c_str());
5195 2 : return nullptr;
5196 : }
5197 114 : if (bAppend)
5198 : {
5199 2 : VSIFSeekL(fpImage, 0, SEEK_END);
5200 2 : nBaseOffset = VSIFTellL(fpImage);
5201 : }
5202 : }
5203 :
5204 278 : auto poDS = std::make_unique<PDS4Dataset>();
5205 139 : poDS->SetDescription(pszFilename);
5206 139 : poDS->m_bMustInitImageFile = true;
5207 139 : poDS->m_fpImage = fpImage;
5208 139 : poDS->m_nBaseOffset = nBaseOffset;
5209 139 : poDS->m_poExternalDS = poExternalDS;
5210 139 : poDS->nRasterXSize = nXSize;
5211 139 : poDS->nRasterYSize = nYSize;
5212 139 : poDS->eAccess = GA_Update;
5213 139 : poDS->m_osImageFilename = std::move(osImageFilename);
5214 139 : poDS->m_bCreateHeader = true;
5215 139 : poDS->m_bStripFileAreaObservationalFromTemplate = true;
5216 139 : poDS->m_osInterleave = pszInterleave;
5217 139 : poDS->m_papszCreationOptions = CSLDuplicate(aosOptions.List());
5218 139 : poDS->m_bUseSrcLabel = aosOptions.FetchBool("USE_SRC_LABEL", true);
5219 139 : poDS->m_bIsLSB = bIsLSB;
5220 139 : poDS->m_osHeaderParsingStandard = std::move(osHeaderParsingStandard);
5221 139 : poDS->m_bCreatedFromExistingBinaryFile = bCreateLabelOnly;
5222 :
5223 139 : if (EQUAL(pszInterleave, "BIP"))
5224 : {
5225 10 : poDS->GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
5226 : GDAL_MDD_IMAGE_STRUCTURE);
5227 : }
5228 129 : else if (EQUAL(pszInterleave, "BSQ"))
5229 : {
5230 128 : poDS->GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "BAND",
5231 : GDAL_MDD_IMAGE_STRUCTURE);
5232 : }
5233 :
5234 338 : for (int i = 0; i < nBandsIn; i++)
5235 : {
5236 199 : if (poDS->m_poExternalDS != nullptr)
5237 : {
5238 : auto poBand = std::make_unique<PDS4WrapperRasterBand>(
5239 24 : poDS->m_poExternalDS->GetRasterBand(i + 1));
5240 24 : poDS->SetBand(i + 1, std::move(poBand));
5241 : }
5242 : else
5243 : {
5244 : auto poBand = std::make_unique<PDS4RawRasterBand>(
5245 175 : poDS.get(), i + 1, poDS->m_fpImage,
5246 175 : poDS->m_nBaseOffset + nBandOffset * i, nPixelOffset,
5247 : nLineOffset, eType,
5248 175 : bIsLSB ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
5249 175 : : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN);
5250 175 : poDS->SetBand(i + 1, std::move(poBand));
5251 : }
5252 : }
5253 :
5254 139 : return poDS;
5255 : }
5256 :
5257 : /************************************************************************/
5258 : /* PDS4GetUnderlyingDataset() */
5259 : /************************************************************************/
5260 :
5261 64 : static GDALDataset *PDS4GetUnderlyingDataset(GDALDataset *poSrcDS)
5262 : {
5263 128 : if (poSrcDS->GetDriver() != nullptr &&
5264 64 : poSrcDS->GetDriver() == GDALGetDriverByName("VRT"))
5265 : {
5266 3 : VRTDataset *poVRTDS = cpl::down_cast<VRTDataset *>(poSrcDS);
5267 3 : poSrcDS = poVRTDS->GetSingleSimpleSource();
5268 : }
5269 :
5270 64 : return poSrcDS;
5271 : }
5272 :
5273 : /************************************************************************/
5274 : /* CreateCopy() */
5275 : /************************************************************************/
5276 :
5277 64 : GDALDataset *PDS4Dataset::CreateCopy(const char *pszFilename,
5278 : GDALDataset *poSrcDS, int bStrict,
5279 : CSLConstList papszOptions,
5280 : GDALProgressFunc pfnProgress,
5281 : void *pProgressData)
5282 : {
5283 : const char *pszImageFormat =
5284 64 : CSLFetchNameValueDef(papszOptions, "IMAGE_FORMAT", "RAW");
5285 64 : GDALDataset *poSrcUnderlyingDS = PDS4GetUnderlyingDataset(poSrcDS);
5286 64 : if (poSrcUnderlyingDS == nullptr)
5287 0 : poSrcUnderlyingDS = poSrcDS;
5288 72 : if (EQUAL(pszImageFormat, "GEOTIFF") &&
5289 8 : strcmp(poSrcUnderlyingDS->GetDescription(),
5290 : CSLFetchNameValueDef(
5291 : papszOptions, "IMAGE_FILENAME",
5292 72 : CPLResetExtensionSafe(pszFilename, "tif").c_str())) == 0)
5293 : {
5294 1 : CPLError(CE_Failure, CPLE_NotSupported,
5295 : "Output file has same name as input file");
5296 1 : return nullptr;
5297 : }
5298 63 : if (poSrcDS->GetRasterCount() == 0)
5299 : {
5300 1 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported band count");
5301 1 : return nullptr;
5302 : }
5303 :
5304 62 : const bool bAppend = CPLFetchBool(papszOptions, "APPEND_SUBDATASET", false);
5305 62 : if (bAppend)
5306 : {
5307 6 : GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
5308 6 : auto poExistingDS = OpenInternal(&oOpenInfo);
5309 6 : if (poExistingDS)
5310 : {
5311 6 : GDALGeoTransform existingGT;
5312 : const bool bExistingHasGT =
5313 6 : poExistingDS->GetGeoTransform(existingGT) == CE_None;
5314 6 : GDALGeoTransform gt;
5315 6 : const bool bSrcHasGT = poSrcDS->GetGeoTransform(gt) == CE_None;
5316 :
5317 6 : CPLString osExistingProj4;
5318 6 : if (const auto poExistingSRS = poExistingDS->GetSpatialRef())
5319 : {
5320 6 : char *pszExistingProj4 = nullptr;
5321 6 : poExistingSRS->exportToProj4(&pszExistingProj4);
5322 6 : if (pszExistingProj4)
5323 6 : osExistingProj4 = pszExistingProj4;
5324 6 : CPLFree(pszExistingProj4);
5325 : }
5326 6 : CPLString osSrcProj4;
5327 6 : if (const auto poSrcSRS = poSrcDS->GetSpatialRef())
5328 : {
5329 6 : char *pszSrcProj4 = nullptr;
5330 6 : poSrcSRS->exportToProj4(&pszSrcProj4);
5331 6 : if (pszSrcProj4)
5332 6 : osSrcProj4 = pszSrcProj4;
5333 6 : CPLFree(pszSrcProj4);
5334 : }
5335 :
5336 6 : poExistingDS.reset();
5337 :
5338 : const auto maxRelErrorGT =
5339 6 : [](const GDALGeoTransform >1, const GDALGeoTransform >2)
5340 : {
5341 6 : double maxRelError = 0.0;
5342 42 : for (int i = 0; i < 6; i++)
5343 : {
5344 36 : if (gt1[i] == 0.0)
5345 : {
5346 12 : maxRelError = std::max(maxRelError, std::abs(gt2[i]));
5347 : }
5348 : else
5349 : {
5350 24 : maxRelError =
5351 48 : std::max(maxRelError, std::abs(gt2[i] - gt1[i]) /
5352 24 : std::abs(gt1[i]));
5353 : }
5354 : }
5355 6 : return maxRelError;
5356 : };
5357 :
5358 6 : if ((bExistingHasGT && !bSrcHasGT) ||
5359 18 : (!bExistingHasGT && bSrcHasGT) ||
5360 6 : (bExistingHasGT && bSrcHasGT &&
5361 6 : maxRelErrorGT(existingGT, gt) > 1e-10))
5362 : {
5363 1 : CPLError(bStrict ? CE_Failure : CE_Warning, CPLE_NotSupported,
5364 : "Appending to a dataset with a different "
5365 : "geotransform is not supported");
5366 1 : if (bStrict)
5367 1 : return nullptr;
5368 : }
5369 : // Do proj string comparison, as it is unlikely that
5370 : // OGRSpatialReference::IsSame() will lead to identical reasons due
5371 : // to PDS changing CRS names, etc...
5372 5 : if (osExistingProj4 != osSrcProj4)
5373 : {
5374 1 : CPLError(bStrict ? CE_Failure : CE_Warning, CPLE_NotSupported,
5375 : "Appending to a dataset with a different "
5376 : "coordinate reference system is not supported");
5377 1 : if (bStrict)
5378 1 : return nullptr;
5379 : }
5380 : }
5381 : }
5382 :
5383 60 : const int nXSize = poSrcDS->GetRasterXSize();
5384 60 : const int nYSize = poSrcDS->GetRasterYSize();
5385 60 : const int nBands = poSrcDS->GetRasterCount();
5386 60 : GDALDataType eType = poSrcDS->GetRasterBand(1)->GetRasterDataType();
5387 : auto poDS = CreateInternal(pszFilename, poSrcDS, nXSize, nYSize, nBands,
5388 120 : eType, papszOptions);
5389 60 : if (poDS == nullptr)
5390 5 : return nullptr;
5391 :
5392 55 : GDALGeoTransform gt;
5393 55 : if (poSrcDS->GetGeoTransform(gt) == CE_None && gt != GDALGeoTransform())
5394 : {
5395 52 : poDS->SetGeoTransform(gt);
5396 : }
5397 :
5398 110 : if (poSrcDS->GetProjectionRef() != nullptr &&
5399 55 : strlen(poSrcDS->GetProjectionRef()) > 0)
5400 : {
5401 52 : poDS->SetProjection(poSrcDS->GetProjectionRef());
5402 : }
5403 :
5404 137 : for (int i = 1; i <= nBands; i++)
5405 : {
5406 82 : int bHasNoData = false;
5407 :
5408 82 : if (poSrcDS->GetRasterBand(i)->GetRasterDataType() == GDT_Int64)
5409 : {
5410 : const auto nNoData =
5411 0 : poSrcDS->GetRasterBand(i)->GetNoDataValueAsInt64(&bHasNoData);
5412 0 : if (bHasNoData)
5413 0 : poDS->GetRasterBand(i)->SetNoDataValueAsInt64(nNoData);
5414 : }
5415 82 : else if (poSrcDS->GetRasterBand(i)->GetRasterDataType() == GDT_UInt64)
5416 : {
5417 : const auto nNoData =
5418 0 : poSrcDS->GetRasterBand(i)->GetNoDataValueAsUInt64(&bHasNoData);
5419 0 : if (bHasNoData)
5420 0 : poDS->GetRasterBand(i)->SetNoDataValueAsUInt64(nNoData);
5421 : }
5422 : else
5423 : {
5424 : const double dfNoData =
5425 82 : poSrcDS->GetRasterBand(i)->GetNoDataValue(&bHasNoData);
5426 82 : if (bHasNoData)
5427 14 : poDS->GetRasterBand(i)->SetNoDataValue(dfNoData);
5428 : }
5429 :
5430 82 : const double dfOffset = poSrcDS->GetRasterBand(i)->GetOffset();
5431 82 : if (dfOffset != 0.0)
5432 3 : poDS->GetRasterBand(i)->SetOffset(dfOffset);
5433 :
5434 82 : const double dfScale = poSrcDS->GetRasterBand(i)->GetScale();
5435 82 : if (dfScale != 1.0)
5436 3 : poDS->GetRasterBand(i)->SetScale(dfScale);
5437 :
5438 164 : poDS->GetRasterBand(i)->SetUnitType(
5439 82 : poSrcDS->GetRasterBand(i)->GetUnitType());
5440 : }
5441 :
5442 55 : if (poDS->m_bUseSrcLabel)
5443 : {
5444 53 : CSLConstList papszMD_PDS4 = poSrcDS->GetMetadata("xml:PDS4");
5445 53 : if (papszMD_PDS4 != nullptr)
5446 : {
5447 6 : poDS->SetMetadata(papszMD_PDS4, "xml:PDS4");
5448 : }
5449 : }
5450 :
5451 55 : if (poDS->m_poExternalDS == nullptr)
5452 : {
5453 : // We don't need to initialize the imagery as we are going to copy it
5454 : // completely
5455 46 : poDS->m_bMustInitImageFile = false;
5456 : }
5457 :
5458 55 : if (!CPLFetchBool(papszOptions, "CREATE_LABEL_ONLY", false))
5459 : {
5460 48 : CPLErr eErr = GDALDatasetCopyWholeRaster(poSrcDS, poDS.get(), nullptr,
5461 : pfnProgress, pProgressData);
5462 48 : poDS->FlushCache(false);
5463 48 : if (eErr != CE_None)
5464 : {
5465 1 : return nullptr;
5466 : }
5467 :
5468 47 : if (CPLFetchBool(papszOptions, "PROPAGATE_SRC_METADATA", true))
5469 : {
5470 46 : CSLConstList papszISIS3MD = poSrcDS->GetMetadata("json:ISIS3");
5471 46 : if (papszISIS3MD)
5472 : {
5473 2 : poDS->SetMetadata(papszISIS3MD, "json:ISIS3");
5474 :
5475 2 : if (poDS->m_poExternalDS)
5476 1 : poDS->m_poExternalDS->SetMetadata(papszISIS3MD,
5477 1 : "json:ISIS3");
5478 : }
5479 : }
5480 : }
5481 :
5482 54 : return poDS.release();
5483 : }
5484 :
5485 : /************************************************************************/
5486 : /* Delete() */
5487 : /************************************************************************/
5488 :
5489 109 : CPLErr PDS4Dataset::Delete(const char *pszFilename)
5490 :
5491 : {
5492 : /* -------------------------------------------------------------------- */
5493 : /* Collect file list. */
5494 : /* -------------------------------------------------------------------- */
5495 218 : GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
5496 218 : auto poDS = PDS4Dataset::OpenInternal(&oOpenInfo);
5497 109 : if (poDS == nullptr)
5498 : {
5499 0 : if (CPLGetLastErrorNo() == 0)
5500 0 : CPLError(CE_Failure, CPLE_OpenFailed,
5501 : "Unable to open %s to obtain file list.", pszFilename);
5502 :
5503 0 : return CE_Failure;
5504 : }
5505 :
5506 109 : char **papszFileList = poDS->GetFileList();
5507 218 : CPLString osImageFilename = poDS->m_osImageFilename;
5508 : bool bCreatedFromExistingBinaryFile =
5509 109 : poDS->m_bCreatedFromExistingBinaryFile;
5510 :
5511 109 : poDS.reset();
5512 :
5513 109 : if (CSLCount(papszFileList) == 0)
5514 : {
5515 0 : CPLError(CE_Failure, CPLE_NotSupported,
5516 : "Unable to determine files associated with %s, "
5517 : "delete fails.",
5518 : pszFilename);
5519 0 : CSLDestroy(papszFileList);
5520 0 : return CE_Failure;
5521 : }
5522 :
5523 : /* -------------------------------------------------------------------- */
5524 : /* Delete all files. */
5525 : /* -------------------------------------------------------------------- */
5526 109 : CPLErr eErr = CE_None;
5527 392 : for (int i = 0; papszFileList[i] != nullptr; ++i)
5528 : {
5529 297 : if (bCreatedFromExistingBinaryFile &&
5530 14 : EQUAL(papszFileList[i], osImageFilename))
5531 : {
5532 7 : continue;
5533 : }
5534 276 : if (VSIUnlink(papszFileList[i]) != 0)
5535 : {
5536 0 : CPLError(CE_Failure, CPLE_AppDefined, "Deleting %s failed:\n%s",
5537 0 : papszFileList[i], VSIStrerror(errno));
5538 0 : eErr = CE_Failure;
5539 : }
5540 : }
5541 :
5542 109 : CSLDestroy(papszFileList);
5543 :
5544 109 : return eErr;
5545 : }
5546 :
5547 : /************************************************************************/
5548 : /* GDALRegister_PDS4() */
5549 : /************************************************************************/
5550 :
5551 2062 : void GDALRegister_PDS4()
5552 :
5553 : {
5554 2062 : if (GDALGetDriverByName(PDS4_DRIVER_NAME) != nullptr)
5555 263 : return;
5556 :
5557 1799 : GDALDriver *poDriver = new GDALDriver();
5558 1799 : PDS4DriverSetCommonMetadata(poDriver);
5559 :
5560 1799 : poDriver->pfnOpen = PDS4Dataset::Open;
5561 1799 : poDriver->pfnCreate = PDS4Dataset::Create;
5562 1799 : poDriver->pfnCreateCopy = PDS4Dataset::CreateCopy;
5563 1799 : poDriver->pfnDelete = PDS4Dataset::Delete;
5564 :
5565 1799 : GetGDALDriverManager()->RegisterDriver(poDriver);
5566 : }
|