Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: FITS Driver
4 : * Purpose: Implement FITS raster read/write support
5 : * Author: Simon Perkins, s.perkins@lanl.gov
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2001, Simon Perkins
9 : * Copyright (c) 2008-2020, Even Rouault <even dot rouault at spatialys.com>
10 : * Copyright (c) 2018, Chiara Marmo <chiara dot marmo at u-psud dot fr>
11 : *
12 : * Permission is hereby granted, free of charge, to any person obtaining a
13 : * copy of this software and associated documentation files (the "Software"),
14 : * to deal in the Software without restriction, including without limitation
15 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16 : * and/or sell copies of the Software, and to permit persons to whom the
17 : * Software is furnished to do so, subject to the following conditions:
18 : *
19 : * The above copyright notice and this permission notice shall be included
20 : * in all copies or substantial portions of the Software.
21 : *
22 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28 : * DEALINGS IN THE SOFTWARE.
29 : ****************************************************************************/
30 :
31 : // So that OFF_T is 64 bits
32 : #define _FILE_OFFSET_BITS 64
33 :
34 : #include "cpl_string.h"
35 : #include "gdal_frmts.h"
36 : #include "gdal_pam.h"
37 : #include "ogr_spatialref.h"
38 : #include "ogrsf_frmts.h"
39 : #include "fitsdrivercore.h"
40 :
41 : #include <string.h>
42 : #include <fitsio.h>
43 :
44 : #include <algorithm>
45 : #include <string>
46 : #include <cstring>
47 : #include <set>
48 : #include <vector>
49 :
50 : /************************************************************************/
51 : /* ==================================================================== */
52 : /* FITSDataset */
53 : /* ==================================================================== */
54 : /************************************************************************/
55 :
56 : class FITSRasterBand;
57 : class FITSLayer;
58 :
59 : class FITSDataset final : public GDALPamDataset
60 : {
61 :
62 : friend class FITSRasterBand;
63 : friend class FITSLayer;
64 :
65 : fitsfile *m_hFITS = nullptr;
66 :
67 : int m_hduNum = 0;
68 : GDALDataType m_gdalDataType = GDT_Unknown; // GDAL code for the image type
69 : int m_fitsDataType = 0; // FITS code for the image type
70 :
71 : bool m_isExistingFile = false;
72 : LONGLONG m_highestOffsetWritten = 0; // How much of image has been written
73 :
74 : bool m_bNoDataChanged = false;
75 : bool m_bNoDataSet = false;
76 : double m_dfNoDataValue = -9999.0;
77 :
78 : bool m_bMetadataChanged = false;
79 :
80 : CPLStringList m_aosSubdatasets{};
81 :
82 : OGRSpatialReference m_oSRS{};
83 :
84 : double m_adfGeoTransform[6];
85 : bool m_bGeoTransformValid = false;
86 :
87 : bool m_bFITSInfoChanged = false;
88 :
89 : std::vector<std::unique_ptr<FITSLayer>> m_apoLayers{};
90 :
91 : CPLErr Init(fitsfile *hFITS, bool isExistingFile, int hduNum);
92 :
93 : void LoadGeoreferencing();
94 : void LoadFITSInfo();
95 : void WriteFITSInfo();
96 : void LoadMetadata(GDALMajorObject *poTarget);
97 :
98 : public:
99 : FITSDataset(); // Others should not call this constructor explicitly
100 : ~FITSDataset();
101 :
102 : static GDALDataset *Open(GDALOpenInfo *);
103 : static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
104 : int nBandsIn, GDALDataType eType,
105 : char **papszParamList);
106 : static CPLErr Delete(const char *pszFilename);
107 :
108 : const OGRSpatialReference *GetSpatialRef() const override;
109 : CPLErr SetSpatialRef(const OGRSpatialReference *poSRS) override;
110 : virtual CPLErr GetGeoTransform(double *) override;
111 : virtual CPLErr SetGeoTransform(double *) override;
112 : char **GetMetadata(const char *papszDomain = nullptr) override;
113 :
114 75 : int GetLayerCount() override
115 : {
116 75 : return static_cast<int>(m_apoLayers.size());
117 : }
118 :
119 : OGRLayer *GetLayer(int) override;
120 :
121 : OGRLayer *ICreateLayer(const char *pszName,
122 : const OGRGeomFieldDefn *poGeomFieldDefn,
123 : CSLConstList papszOptions) override;
124 :
125 : int TestCapability(const char *pszCap) override;
126 :
127 : bool GetRawBinaryLayout(GDALDataset::RawBinaryLayout &) override;
128 : };
129 :
130 : /************************************************************************/
131 : /* ==================================================================== */
132 : /* FITSRasterBand */
133 : /* ==================================================================== */
134 : /************************************************************************/
135 :
136 : class FITSRasterBand final : public GDALPamRasterBand
137 : {
138 :
139 : friend class FITSDataset;
140 :
141 : bool m_bHaveOffsetScale = false;
142 : double m_dfOffset = 0.0;
143 : double m_dfScale = 1.0;
144 :
145 : protected:
146 : FITSDataset *m_poFDS = nullptr;
147 :
148 : bool m_bNoDataSet = false;
149 : double m_dfNoDataValue = -9999.0;
150 :
151 : public:
152 : FITSRasterBand(FITSDataset *, int);
153 : virtual ~FITSRasterBand();
154 :
155 : virtual CPLErr IReadBlock(int, int, void *) override;
156 : virtual CPLErr IWriteBlock(int, int, void *) override;
157 :
158 : virtual double GetNoDataValue(int *) override final;
159 : virtual CPLErr SetNoDataValue(double) override final;
160 : virtual CPLErr DeleteNoDataValue() override final;
161 :
162 : virtual double GetOffset(int *pbSuccess = nullptr) override final;
163 : virtual CPLErr SetOffset(double dfNewValue) override final;
164 : virtual double GetScale(int *pbSuccess = nullptr) override final;
165 : virtual CPLErr SetScale(double dfNewValue) override final;
166 : };
167 :
168 : /************************************************************************/
169 : /* ==================================================================== */
170 : /* FITSLayer */
171 : /* ==================================================================== */
172 : /************************************************************************/
173 : namespace gdal::FITS
174 : {
175 : struct ColDesc
176 : {
177 : std::string typechar{};
178 : int iCol = 0; // numbering starting at 1
179 : int iBit = 0; // numbering starting at 1
180 : int nRepeat = 0;
181 : int nItems = 1;
182 : double dfOffset = 0;
183 : double dfScale = 1;
184 : bool bHasNull = false;
185 : LONGLONG nNullValue = 0;
186 : int nTypeCode = 0; // unset
187 : };
188 : } // namespace gdal::FITS
189 :
190 : using namespace gdal::FITS;
191 :
192 : class FITSLayer final : public OGRLayer,
193 : public OGRGetNextFeatureThroughRaw<FITSLayer>
194 : {
195 : friend class FITSDataset;
196 : FITSDataset *m_poDS = nullptr;
197 : int m_hduNum = 0;
198 : OGRFeatureDefn *m_poFeatureDefn = nullptr;
199 : LONGLONG m_nCurRow = 1;
200 : LONGLONG m_nRows = 0;
201 :
202 : std::vector<ColDesc> m_aoColDescs;
203 :
204 : CPLStringList m_aosCreationOptions{};
205 :
206 : std::vector<int> m_anDeferredFieldsIndices{};
207 :
208 : OGRFeature *GetNextRawFeature();
209 : void SetActiveHDU();
210 : void RunDeferredFieldCreation(const OGRFeature *poFeature = nullptr);
211 : bool SetOrCreateFeature(const OGRFeature *poFeature, LONGLONG nRow);
212 :
213 : public:
214 : FITSLayer(FITSDataset *poDS, int hduNum, const char *pszExtName);
215 : ~FITSLayer();
216 :
217 63 : OGRFeatureDefn *GetLayerDefn() override
218 : {
219 63 : return m_poFeatureDefn;
220 : }
221 :
222 : void ResetReading() override;
223 : int TestCapability(const char *) override;
224 : OGRFeature *GetFeature(GIntBig) override;
225 : GIntBig GetFeatureCount(int bForce) override;
226 : OGRErr CreateField(const OGRFieldDefn *poField, int bApproxOK) override;
227 : OGRErr ICreateFeature(OGRFeature *poFeature) override;
228 : OGRErr ISetFeature(OGRFeature *poFeature) override;
229 : OGRErr DeleteFeature(GIntBig nFID) override;
230 :
231 23 : DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(FITSLayer)
232 :
233 4 : void SetCreationOptions(CSLConstList papszOptions)
234 : {
235 4 : m_aosCreationOptions = papszOptions;
236 4 : }
237 : };
238 :
239 : /************************************************************************/
240 : /* FITSLayer() */
241 : /************************************************************************/
242 :
243 16 : FITSLayer::FITSLayer(FITSDataset *poDS, int hduNum, const char *pszExtName)
244 16 : : m_poDS(poDS), m_hduNum(hduNum)
245 : {
246 16 : if (pszExtName[0] != 0)
247 14 : m_poFeatureDefn = new OGRFeatureDefn(pszExtName);
248 : else
249 2 : m_poFeatureDefn =
250 2 : new OGRFeatureDefn(CPLSPrintf("Table HDU %d", hduNum));
251 16 : m_poFeatureDefn->Reference();
252 16 : m_poFeatureDefn->SetGeomType(wkbNone);
253 16 : SetDescription(m_poFeatureDefn->GetName());
254 :
255 16 : SetActiveHDU();
256 :
257 16 : m_poDS->LoadMetadata(this);
258 :
259 16 : int status = 0;
260 16 : fits_get_num_rowsll(m_poDS->m_hFITS, &m_nRows, &status);
261 16 : if (status)
262 : {
263 0 : CPLError(CE_Failure, CPLE_AppDefined, "fits_get_num_rowsll() failed");
264 : }
265 16 : int nCols = 0;
266 16 : status = 0;
267 16 : fits_get_num_cols(m_poDS->m_hFITS, &nCols, &status);
268 16 : if (status)
269 : {
270 0 : CPLError(CE_Failure, CPLE_AppDefined, "fits_get_num_cols() failed");
271 : }
272 :
273 32 : std::vector<std::string> aosNames(nCols);
274 32 : std::vector<char *> apszNames(nCols);
275 460 : for (int i = 0; i < nCols; i++)
276 : {
277 444 : aosNames[i].resize(80);
278 444 : apszNames[i] = &aosNames[i][0];
279 : }
280 :
281 16 : status = 0;
282 16 : fits_read_btblhdrll(m_poDS->m_hFITS, nCols, nullptr, nullptr,
283 : apszNames.data(), nullptr, nullptr, nullptr, nullptr,
284 : &status);
285 16 : if (status)
286 : {
287 0 : CPLError(CE_Failure, CPLE_AppDefined, "fits_read_btblhdrll() failed");
288 : }
289 :
290 460 : for (int i = 0; i < nCols; i++)
291 : {
292 444 : aosNames[i].resize(strlen(aosNames[i].c_str()));
293 :
294 : char typechar[80];
295 444 : LONGLONG nRepeat = 0;
296 444 : double dfScale = 0;
297 444 : double dfOffset = 0;
298 444 : status = 0;
299 444 : fits_get_bcolparmsll(m_poDS->m_hFITS, i + 1,
300 : nullptr, // column name
301 : nullptr, // unit
302 : typechar, &nRepeat, &dfScale, &dfOffset,
303 : nullptr, // nulval
304 : nullptr, // tdisp
305 : &status);
306 444 : if (status)
307 : {
308 0 : CPLError(CE_Failure, CPLE_AppDefined,
309 : "fits_get_bcolparmsll() failed");
310 : }
311 :
312 444 : ColDesc oCol;
313 :
314 444 : status = 0;
315 444 : fits_read_key(m_poDS->m_hFITS, TLONGLONG, CPLSPrintf("TNULL%d", i + 1),
316 : &oCol.nNullValue, nullptr, &status);
317 444 : oCol.bHasNull = status == 0;
318 :
319 444 : OGRFieldType eType = OFTString;
320 444 : OGRFieldSubType eSubType = OFSTNone;
321 444 : if (typechar[0] == 'L') // Logical
322 : {
323 12 : eType = OFTInteger;
324 12 : eSubType = OFSTBoolean;
325 : }
326 432 : else if (typechar[0] == 'X') // Bit array
327 : {
328 20 : if (nRepeat > 128)
329 : {
330 0 : CPLDebug("FITS", "Too large repetition count for column %s",
331 0 : aosNames[i].c_str());
332 0 : continue;
333 : }
334 360 : for (int j = 1; j <= nRepeat; j++)
335 : {
336 : OGRFieldDefn oFieldDefn(
337 340 : (aosNames[i] + CPLSPrintf("_bit%d", j)).c_str(),
338 680 : OFTInteger);
339 : // cppcheck-suppress danglingTemporaryLifetime
340 340 : m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
341 :
342 680 : ColDesc oColBit;
343 340 : oColBit.typechar = typechar;
344 340 : oColBit.iCol = i + 1;
345 340 : oColBit.iBit = j;
346 340 : m_aoColDescs.emplace_back(oColBit);
347 : }
348 20 : continue;
349 : }
350 412 : else if (typechar[0] == 'B') // Unsigned byte
351 : {
352 30 : if (dfOffset == -128 && dfScale == 1)
353 : {
354 6 : eType = OFTInteger; // signed byte
355 6 : oCol.nTypeCode = TSBYTE;
356 : // fits_read_col() automatically offsets numeric values
357 6 : dfOffset = 0;
358 : }
359 24 : else if (dfOffset != 0 || dfScale != 1)
360 6 : eType = OFTReal;
361 : else
362 18 : eType = OFTInteger;
363 : }
364 382 : else if (typechar[0] == 'I') // 16-bit signed integer
365 : {
366 31 : if (dfOffset == 32768.0 && dfScale == 1)
367 : {
368 6 : eType = OFTInteger; // unsigned 16-bit integer
369 6 : oCol.nTypeCode = TUSHORT;
370 : // fits_read_col() automatically offsets numeric values
371 6 : dfOffset = 0;
372 : }
373 25 : else if (dfOffset != 0 || dfScale != 1)
374 6 : eType = OFTReal;
375 : else
376 : {
377 19 : eType = OFTInteger;
378 19 : eSubType = OFSTInt16;
379 : }
380 : }
381 351 : else if (typechar[0] == 'J') // 32-bit signed integer
382 : {
383 58 : if (dfOffset == 2147483648.0 && dfScale == 1)
384 : {
385 6 : eType = OFTInteger64; // unsigned 32-bit integer --> needs to
386 : // promote to 64 bits
387 6 : oCol.nTypeCode = TUINT;
388 : // fits_read_col() automatically offsets numeric values
389 6 : dfOffset = 0;
390 : }
391 52 : else if (dfOffset != 0 || dfScale != 1)
392 6 : eType = OFTReal;
393 : else
394 46 : eType = OFTInteger;
395 : }
396 293 : else if (typechar[0] == 'K') // 64-bit signed integer
397 : {
398 29 : if (dfOffset != 0 || dfScale != 1)
399 6 : eType = OFTReal;
400 : else
401 23 : eType = OFTInteger64;
402 : }
403 264 : else if (typechar[0] == 'A') // Character
404 : {
405 :
406 26 : status = 0;
407 26 : LONGLONG nWidth = 0;
408 26 : fits_get_coltypell(m_poDS->m_hFITS, i + 1,
409 : nullptr, // typecode
410 : nullptr, // repeat
411 : &nWidth, &status);
412 26 : if (status)
413 : {
414 0 : CPLError(CE_Failure, CPLE_AppDefined,
415 : "fits_get_coltypell() failed");
416 : }
417 26 : if (nRepeat >= 2 * nWidth && nWidth != 0)
418 : {
419 6 : oCol.nItems = static_cast<int>(nRepeat / nWidth);
420 6 : eType = OFTStringList;
421 6 : nRepeat = nWidth;
422 : }
423 : else
424 : {
425 20 : eType = OFTString;
426 : }
427 : }
428 238 : else if (typechar[0] == 'E') // IEEE754 32bit
429 : {
430 25 : eType = OFTReal;
431 25 : if (dfOffset == 0 && dfScale == 1)
432 19 : eSubType = OFSTFloat32;
433 : // fits_read_col() automatically scales numeric values
434 25 : dfOffset = 0;
435 25 : dfScale = 1;
436 : }
437 213 : else if (typechar[0] == 'D') // IEEE754 64bit
438 : {
439 51 : eType = OFTReal;
440 : // fits_read_col() automatically scales numeric values
441 51 : dfOffset = 0;
442 51 : dfScale = 1;
443 : }
444 162 : else if (typechar[0] == 'C') // IEEE754 32bit complex
445 : {
446 20 : eType = OFTString;
447 : // fits_read_col() automatically scales numeric values
448 20 : dfOffset = 0;
449 20 : dfScale = 1;
450 : }
451 142 : else if (typechar[0] == 'M') // IEEE754 64bit complex
452 : {
453 20 : eType = OFTString;
454 : // fits_read_col() automatically scales numeric values
455 20 : dfOffset = 0;
456 20 : dfScale = 1;
457 : }
458 122 : else if (typechar[0] == 'P' || typechar[0] == 'Q') // Array
459 : {
460 122 : if (typechar[1] == 'L')
461 : {
462 12 : nRepeat = 0;
463 12 : eType = OFTIntegerList;
464 12 : eSubType = OFSTBoolean;
465 : }
466 110 : else if (typechar[1] == 'B')
467 : {
468 6 : nRepeat = 0;
469 6 : eType = OFTIntegerList;
470 : }
471 104 : else if (typechar[1] == 'I')
472 : {
473 11 : nRepeat = 0;
474 11 : eType = OFTIntegerList;
475 11 : eSubType = OFSTInt16;
476 : }
477 93 : else if (typechar[1] == 'J')
478 : {
479 26 : nRepeat = 0;
480 26 : eType = OFTIntegerList;
481 : }
482 67 : else if (typechar[1] == 'K')
483 : {
484 11 : nRepeat = 0;
485 11 : eType = OFTInteger64List;
486 : }
487 56 : else if (typechar[1] == 'A')
488 : {
489 22 : eType = OFTString;
490 : }
491 34 : else if (typechar[1] == 'E')
492 : {
493 11 : nRepeat = 0;
494 11 : eType = OFTRealList;
495 11 : if (dfOffset == 0 && dfScale == 1)
496 11 : eSubType = OFSTFloat32;
497 : // fits_read_col() automatically scales numeric values
498 11 : dfOffset = 0;
499 11 : dfScale = 1;
500 : }
501 23 : else if (typechar[1] == 'D')
502 : {
503 11 : nRepeat = 0;
504 11 : eType = OFTRealList;
505 : // fits_read_col() automatically scales numeric values
506 11 : dfOffset = 0;
507 11 : dfScale = 1;
508 : }
509 12 : else if (typechar[1] == 'C')
510 : {
511 6 : nRepeat = 0;
512 6 : eType = OFTStringList;
513 : // fits_read_col() automatically scales numeric values
514 6 : dfOffset = 0;
515 6 : dfScale = 1;
516 : }
517 6 : else if (typechar[1] == 'M')
518 : {
519 6 : nRepeat = 0;
520 6 : eType = OFTStringList;
521 : // fits_read_col() automatically scales numeric values
522 6 : dfOffset = 0;
523 6 : dfScale = 1;
524 : }
525 : else
526 : {
527 0 : CPLDebug("FITS", "Unhandled type %s", typechar);
528 0 : continue;
529 : }
530 : }
531 : else
532 : {
533 0 : CPLDebug("FITS", "Unhandled type %s", typechar);
534 0 : continue;
535 : }
536 :
537 424 : if (nRepeat > 1 && typechar[0] != 'A')
538 : {
539 84 : if (eType == OFTInteger)
540 45 : eType = OFTIntegerList;
541 39 : else if (eType == OFTInteger64)
542 9 : eType = OFTInteger64List;
543 30 : else if (eType == OFTReal)
544 18 : eType = OFTRealList;
545 12 : else if (eType == OFTString)
546 12 : eType = OFTStringList;
547 : }
548 :
549 848 : OGRFieldDefn oFieldDefn(aosNames[i].c_str(), eType);
550 424 : oFieldDefn.SetSubType(eSubType);
551 424 : if (typechar[0] == 'A')
552 26 : oFieldDefn.SetWidth(static_cast<int>(nRepeat));
553 424 : m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
554 :
555 424 : oCol.typechar = typechar;
556 424 : oCol.iCol = i + 1;
557 424 : oCol.nRepeat = static_cast<int>(nRepeat);
558 424 : oCol.dfOffset = dfOffset;
559 424 : oCol.dfScale = dfScale;
560 424 : m_aoColDescs.emplace_back(oCol);
561 : }
562 16 : }
563 :
564 : /************************************************************************/
565 : /* ~FITSLayer() */
566 : /************************************************************************/
567 :
568 32 : FITSLayer::~FITSLayer()
569 : {
570 16 : RunDeferredFieldCreation();
571 :
572 18 : for (int i = 0; i < m_aosCreationOptions.size(); i++)
573 : {
574 2 : if (STARTS_WITH_CI(m_aosCreationOptions[i], "REPEAT_"))
575 : {
576 1 : char *pszKey = nullptr;
577 1 : CPL_IGNORE_RET_VAL(
578 1 : CPLParseNameValue(m_aosCreationOptions[i], &pszKey));
579 1 : if (pszKey &&
580 1 : m_poFeatureDefn->GetFieldIndex(pszKey + strlen("REPEAT_")) < 0)
581 : {
582 0 : CPLError(CE_Warning, CPLE_AppDefined,
583 : "Creation option %s ignored as field does not exist",
584 : m_aosCreationOptions[i]);
585 : }
586 1 : CPLFree(pszKey);
587 : }
588 : }
589 :
590 16 : m_poFeatureDefn->Release();
591 32 : }
592 :
593 : /************************************************************************/
594 : /* SetActiveHDU() */
595 : /************************************************************************/
596 :
597 73 : void FITSLayer::SetActiveHDU()
598 : {
599 73 : int status = 0;
600 73 : fits_movabs_hdu(m_poDS->m_hFITS, m_hduNum, nullptr, &status);
601 73 : if (status != 0)
602 : {
603 0 : CPLError(CE_Failure, CPLE_AppDefined, "fits_movabs_hdu() failed: %d",
604 : status);
605 : }
606 73 : }
607 :
608 : /************************************************************************/
609 : /* GetFeatureCount() */
610 : /************************************************************************/
611 :
612 7 : GIntBig FITSLayer::GetFeatureCount(int bForce)
613 : {
614 7 : if (m_poAttrQuery == nullptr && m_poFilterGeom == nullptr)
615 7 : return m_nRows;
616 0 : return OGRLayer::GetFeatureCount(bForce);
617 : }
618 :
619 : /************************************************************************/
620 : /* ResetReading() */
621 : /************************************************************************/
622 :
623 4 : void FITSLayer::ResetReading()
624 : {
625 4 : m_nCurRow = 1;
626 4 : }
627 :
628 : /************************************************************************/
629 : /* ReadCol */
630 : /************************************************************************/
631 :
632 : template <typename T_FITS, typename T_GDAL, int TYPECODE> struct ReadCol
633 : {
634 545 : static void Read(fitsfile *hFITS, const ColDesc &colDesc, int iField,
635 : LONGLONG irow, OGRFeature *poFeature, int nRepeat)
636 : {
637 545 : int status = 0;
638 1090 : std::vector<T_FITS> x(nRepeat);
639 545 : fits_read_col(hFITS, TYPECODE, colDesc.iCol, irow, 1, nRepeat, nullptr,
640 : x.data(), nullptr, &status);
641 561 : if (nRepeat == 1 && colDesc.bHasNull &&
642 16 : x[0] == static_cast<T_FITS>(colDesc.nNullValue))
643 : {
644 5 : poFeature->SetFieldNull(iField);
645 : }
646 540 : else if (colDesc.dfScale != 1.0 || colDesc.dfOffset != 0.0)
647 : {
648 128 : std::vector<double> scaled;
649 64 : scaled.reserve(nRepeat);
650 128 : for (int i = 0; i < nRepeat; ++i)
651 : {
652 128 : scaled.push_back(static_cast<double>(x[i]) * colDesc.dfScale +
653 64 : colDesc.dfOffset);
654 : }
655 128 : poFeature->SetField(iField, nRepeat, scaled.data());
656 : }
657 476 : else if (nRepeat == 1)
658 : {
659 202 : poFeature->SetField(iField, static_cast<T_GDAL>(x[0]));
660 : }
661 : else
662 : {
663 548 : std::vector<T_GDAL> xGDAL;
664 274 : xGDAL.reserve(nRepeat);
665 890 : for (int i = 0; i < nRepeat; i++)
666 616 : xGDAL.push_back(x[i]);
667 274 : poFeature->SetField(iField, nRepeat, xGDAL.data());
668 : }
669 545 : }
670 : };
671 :
672 : /************************************************************************/
673 : /* GetNextRawFeature() */
674 : /************************************************************************/
675 :
676 23 : OGRFeature *FITSLayer::GetNextRawFeature()
677 : {
678 23 : auto poFeature = GetFeature(m_nCurRow);
679 23 : if (poFeature)
680 19 : m_nCurRow++;
681 23 : return poFeature;
682 : }
683 :
684 : /************************************************************************/
685 : /* GetFeature() */
686 : /************************************************************************/
687 :
688 29 : OGRFeature *FITSLayer::GetFeature(GIntBig nFID)
689 : {
690 29 : LONGLONG nRow = static_cast<LONGLONG>(nFID);
691 29 : if (nRow <= 0 || nRow > m_nRows)
692 6 : return nullptr;
693 :
694 23 : RunDeferredFieldCreation();
695 :
696 23 : OGRFeature *poFeature = new OGRFeature(m_poFeatureDefn);
697 :
698 23 : SetActiveHDU();
699 :
700 1761 : const auto ReadField = [this, &poFeature, nRow](const ColDesc &colDesc,
701 : int iField, char typechar,
702 3009 : int nRepeat)
703 : {
704 1761 : int status = 0;
705 1761 : if (typechar == 'L')
706 : {
707 128 : std::vector<char> x(nRepeat);
708 64 : fits_read_col(m_poDS->m_hFITS, TLOGICAL, colDesc.iCol, nRow, 1,
709 64 : nRepeat, nullptr, x.data(), nullptr, &status);
710 64 : if (nRepeat == 1)
711 : {
712 16 : poFeature->SetField(iField, x[0] == '1' ? 1 : 0);
713 : }
714 : else
715 : {
716 96 : std::vector<int> intValues;
717 48 : intValues.reserve(nRepeat);
718 134 : for (int i = 0; i < nRepeat; ++i)
719 : {
720 86 : intValues.push_back(x[i] == '1' ? 1 : 0);
721 : }
722 48 : poFeature->SetField(iField, nRepeat, intValues.data());
723 : }
724 : }
725 1697 : else if (typechar == 'X')
726 : {
727 782 : char x = 0;
728 782 : fits_read_col_bit(m_poDS->m_hFITS, colDesc.iCol, nRow, colDesc.iBit,
729 : 1, &x, &status);
730 782 : poFeature->SetField(iField, x);
731 : }
732 915 : else if (typechar == 'B')
733 : {
734 96 : if (colDesc.nTypeCode == TSBYTE)
735 : {
736 16 : ReadCol<signed char, int, TSBYTE>::Read(
737 16 : m_poDS->m_hFITS, colDesc, iField, nRow, poFeature, nRepeat);
738 : }
739 : else
740 : {
741 80 : ReadCol<GByte, int, TBYTE>::Read(
742 80 : m_poDS->m_hFITS, colDesc, iField, nRow, poFeature, nRepeat);
743 : }
744 : }
745 819 : else if (typechar == 'I')
746 : {
747 101 : if (colDesc.nTypeCode == TUSHORT)
748 : {
749 16 : ReadCol<GUInt16, int, TUSHORT>::Read(
750 16 : m_poDS->m_hFITS, colDesc, iField, nRow, poFeature, nRepeat);
751 : }
752 : else
753 : {
754 85 : ReadCol<GInt16, int, TSHORT>::Read(
755 85 : m_poDS->m_hFITS, colDesc, iField, nRow, poFeature, nRepeat);
756 : }
757 : }
758 718 : else if (typechar == 'J')
759 : {
760 171 : if (colDesc.nTypeCode == TUINT)
761 : {
762 16 : ReadCol<GUInt32, GIntBig, TUINT>::Read(
763 16 : m_poDS->m_hFITS, colDesc, iField, nRow, poFeature, nRepeat);
764 : }
765 : else
766 : {
767 155 : ReadCol<GInt32, int, TINT>::Read(
768 155 : m_poDS->m_hFITS, colDesc, iField, nRow, poFeature, nRepeat);
769 : }
770 : }
771 547 : else if (typechar == 'K')
772 : {
773 92 : ReadCol<GInt64, GIntBig, TLONGLONG>::Read(
774 92 : m_poDS->m_hFITS, colDesc, iField, nRow, poFeature, nRepeat);
775 : }
776 455 : else if (typechar == 'A') // Character
777 : {
778 109 : if (colDesc.nItems > 1)
779 : {
780 32 : CPLStringList aosList;
781 64 : for (int iItem = 1; iItem <= colDesc.nItems; iItem++)
782 : {
783 96 : std::string osStr;
784 48 : if (nRepeat)
785 : {
786 48 : osStr.resize(nRepeat);
787 48 : char *pszStr = &osStr[0];
788 48 : fits_read_col_str(m_poDS->m_hFITS, colDesc.iCol, nRow,
789 : iItem, 1, nullptr, &pszStr, nullptr,
790 : &status);
791 : }
792 48 : aosList.AddString(osStr.c_str());
793 : }
794 16 : poFeature->SetField(iField, aosList.List());
795 : }
796 : else
797 : {
798 186 : std::string osStr;
799 93 : if (nRepeat)
800 : {
801 93 : osStr.resize(nRepeat);
802 93 : char *pszStr = &osStr[0];
803 93 : fits_read_col_str(m_poDS->m_hFITS, colDesc.iCol, nRow, 1, 1,
804 : nullptr, &pszStr, nullptr, &status);
805 : }
806 93 : poFeature->SetField(iField, osStr.c_str());
807 : }
808 : }
809 346 : else if (typechar == 'E') // IEEE754 32bit
810 : {
811 85 : ReadCol<float, double, TFLOAT>::Read(
812 85 : m_poDS->m_hFITS, colDesc, iField, nRow, poFeature, nRepeat);
813 : }
814 261 : else if (typechar == 'D') // IEEE754 64bit
815 : {
816 258 : std::vector<double> x(nRepeat);
817 129 : fits_read_col(m_poDS->m_hFITS, TDOUBLE, colDesc.iCol, nRow, 1,
818 129 : nRepeat, nullptr, x.data(), nullptr, &status);
819 129 : if (nRepeat == 1)
820 83 : poFeature->SetField(iField, x[0]);
821 : else
822 46 : poFeature->SetField(iField, nRepeat, x.data());
823 : }
824 132 : else if (typechar == 'C') // IEEE754 32bit complex
825 : {
826 132 : std::vector<float> x(2 * nRepeat);
827 66 : fits_read_col(m_poDS->m_hFITS, TCOMPLEX, colDesc.iCol, nRow, 1,
828 66 : nRepeat, nullptr, x.data(), nullptr, &status);
829 132 : CPLStringList aosList;
830 159 : for (int i = 0; i < nRepeat; ++i)
831 : aosList.AddString(
832 93 : CPLSPrintf("%.18g + %.18gj", x[2 * i + 0], x[2 * i + 1]));
833 66 : if (nRepeat == 1)
834 34 : poFeature->SetField(iField, aosList[0]);
835 : else
836 32 : poFeature->SetField(iField, aosList.List());
837 : }
838 66 : else if (typechar == 'M') // IEEE754 64bit complex
839 : {
840 132 : std::vector<double> x(2 * nRepeat);
841 66 : fits_read_col(m_poDS->m_hFITS, TDBLCOMPLEX, colDesc.iCol, nRow, 1,
842 66 : nRepeat, nullptr, x.data(), nullptr, &status);
843 132 : CPLStringList aosList;
844 159 : for (int i = 0; i < nRepeat; ++i)
845 : {
846 : aosList.AddString(
847 93 : CPLSPrintf("%.18g + %.18gj", x[2 * i + 0], x[2 * i + 1]));
848 : }
849 66 : if (nRepeat == 1)
850 34 : poFeature->SetField(iField, aosList[0]);
851 : else
852 32 : poFeature->SetField(iField, aosList.List());
853 : }
854 : else
855 : {
856 0 : CPLDebug("FITS", "Unhandled typechar %c", typechar);
857 : }
858 1761 : if (status)
859 : {
860 0 : CPLError(CE_Failure, CPLE_AppDefined, "fits_read_col() failed");
861 : }
862 1761 : };
863 :
864 23 : const int nFieldCount = poFeature->GetFieldCount();
865 1784 : for (int iField = 0; iField < nFieldCount; iField++)
866 : {
867 1761 : const auto &colDesc = m_aoColDescs[iField];
868 1761 : if (colDesc.typechar[0] == 'P' || colDesc.typechar[0] == 'Q')
869 : {
870 295 : int status = 0;
871 295 : LONGLONG nRepeat = 0;
872 295 : fits_read_descriptll(m_poDS->m_hFITS, colDesc.iCol, nRow, &nRepeat,
873 : nullptr, &status);
874 295 : ReadField(colDesc, iField, colDesc.typechar[1],
875 : static_cast<int>(nRepeat));
876 : }
877 : else
878 : {
879 1466 : ReadField(colDesc, iField, colDesc.typechar[0], colDesc.nRepeat);
880 : }
881 : }
882 23 : poFeature->SetFID(nRow);
883 23 : return poFeature;
884 : }
885 :
886 : /************************************************************************/
887 : /* TestCapability() */
888 : /************************************************************************/
889 :
890 353 : int FITSLayer::TestCapability(const char *pszCap)
891 : {
892 353 : if (EQUAL(pszCap, OLCFastFeatureCount))
893 1 : return m_poAttrQuery == nullptr && m_poFilterGeom == nullptr;
894 :
895 352 : if (EQUAL(pszCap, OLCRandomRead))
896 1 : return true;
897 :
898 351 : if (EQUAL(pszCap, OLCCreateField) || EQUAL(pszCap, OLCSequentialWrite) ||
899 25 : EQUAL(pszCap, OLCRandomWrite) || EQUAL(pszCap, OLCDeleteFeature))
900 : {
901 332 : return m_poDS->GetAccess() == GA_Update;
902 : }
903 :
904 19 : return false;
905 : }
906 :
907 : /************************************************************************/
908 : /* RunDeferredFieldCreation() */
909 : /************************************************************************/
910 :
911 55 : void FITSLayer::RunDeferredFieldCreation(const OGRFeature *poFeature)
912 : {
913 55 : if (m_anDeferredFieldsIndices.empty())
914 50 : return;
915 :
916 5 : SetActiveHDU();
917 :
918 10 : CPLString osPendingBitFieldName{};
919 5 : int nPendingBitFieldSize = 0;
920 10 : std::set<CPLString> oSetBitFieldNames{};
921 :
922 161 : const auto FlushCreationPendingBitField = [this, &osPendingBitFieldName,
923 : &nPendingBitFieldSize,
924 505 : &oSetBitFieldNames]()
925 : {
926 161 : if (osPendingBitFieldName.empty())
927 153 : return;
928 :
929 : const int iCol =
930 8 : m_aoColDescs.empty() ? 1 : m_aoColDescs.back().iCol + 1;
931 144 : for (int iBit = 1; iBit <= nPendingBitFieldSize; iBit++)
932 : {
933 272 : ColDesc oCol;
934 136 : oCol.iCol = iCol;
935 136 : oCol.iBit = iBit;
936 136 : oCol.typechar = 'X';
937 136 : m_aoColDescs.emplace_back(oCol);
938 : }
939 :
940 8 : int status = 0;
941 8 : CPLString osTForm;
942 8 : osTForm.Printf("%dX", nPendingBitFieldSize);
943 8 : fits_insert_col(m_poDS->m_hFITS, iCol, &osPendingBitFieldName[0],
944 8 : &osTForm[0], &status);
945 8 : if (status != 0)
946 : {
947 0 : CPLError(CE_Failure, CPLE_AppDefined,
948 : "fits_insert_col() failed: %d", status);
949 : }
950 :
951 8 : oSetBitFieldNames.insert(osPendingBitFieldName);
952 8 : osPendingBitFieldName.clear();
953 8 : nPendingBitFieldSize = 0;
954 5 : };
955 :
956 : const bool bRepeatFromFirstFeature =
957 9 : poFeature != nullptr &&
958 4 : EQUAL(m_aosCreationOptions.FetchNameValueDef("COMPUTE_REPEAT",
959 : "AT_FIELD_CREATION"),
960 : "AT_FIRST_FEATURE_CREATION");
961 :
962 5 : char **papszMD = GetMetadata();
963 5 : bool bFirstMD = true;
964 :
965 10 : std::map<CPLString, std::map<CPLString, CPLString>> oMapColNameToMetadata;
966 :
967 : // Remap column related metadata (likely coming from source FITS) to
968 : // actual column numbers
969 10 : std::map<int, CPLString> oMapFITSMDColToName;
970 211 : for (auto papszIter = papszMD; papszIter && *papszIter; ++papszIter)
971 : {
972 206 : char *pszKey = nullptr;
973 206 : const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
974 206 : if (pszKey && pszValue)
975 : {
976 206 : bool bIgnore = false;
977 341 : for (const char *pszPrefix :
978 : {"TTYPE", "TFORM", "TUNIT", "TNULL", "TSCAL", "TZERO", "TDISP",
979 : "TDIM", "TBCOL", "TCTYP", "TCUNI", "TCRPX", "TCRVL", "TCDLT",
980 547 : "TRPOS"})
981 : {
982 537 : if (STARTS_WITH(pszKey, pszPrefix))
983 : {
984 196 : const char *pszCol = pszKey + strlen(pszPrefix);
985 196 : const int nCol = atoi(pszCol);
986 196 : if (!EQUAL(pszPrefix, "TTYPE"))
987 : {
988 109 : auto oIter = oMapFITSMDColToName.find(nCol);
989 218 : CPLString osColName;
990 109 : if (oIter == oMapFITSMDColToName.end())
991 : {
992 87 : const char *pszColName = CSLFetchNameValue(
993 : papszMD,
994 174 : (std::string("TTYPE") + pszCol).c_str());
995 87 : if (pszColName)
996 : {
997 87 : osColName = pszColName;
998 87 : osColName.Trim();
999 87 : oMapFITSMDColToName[nCol] = osColName;
1000 : }
1001 : }
1002 : else
1003 : {
1004 22 : osColName = oIter->second;
1005 : }
1006 109 : if (!osColName.empty())
1007 : {
1008 218 : oMapColNameToMetadata[osColName][pszPrefix] =
1009 327 : CPLString(pszValue).Trim();
1010 : }
1011 : }
1012 196 : bIgnore = true;
1013 196 : break;
1014 : }
1015 : }
1016 :
1017 206 : if (!bIgnore && strlen(pszKey) <= 8 && !EQUAL(pszKey, "TFIELDS") &&
1018 5 : !EQUAL(pszKey, "EXTNAME"))
1019 : {
1020 0 : if (bFirstMD)
1021 : {
1022 0 : int status = 0;
1023 0 : fits_write_key_longwarn(m_poDS->m_hFITS, &status);
1024 0 : bFirstMD = false;
1025 : }
1026 :
1027 0 : char *pszValueNonConst = const_cast<char *>(pszValue);
1028 0 : int status = 0;
1029 0 : fits_update_key_longstr(m_poDS->m_hFITS, pszKey,
1030 : pszValueNonConst, nullptr, &status);
1031 : }
1032 : }
1033 206 : CPLFree(pszKey);
1034 : }
1035 :
1036 298 : for (const int nFieldIdx : m_anDeferredFieldsIndices)
1037 : {
1038 293 : const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(nFieldIdx);
1039 293 : const auto &oFieldDefn = *poFieldDefn;
1040 293 : const char *pszFieldName = oFieldDefn.GetNameRef();
1041 293 : const OGRFieldType eType = oFieldDefn.GetType();
1042 293 : const OGRFieldSubType eSubType = oFieldDefn.GetSubType();
1043 :
1044 293 : if (eType == OFTInteger)
1045 : {
1046 160 : const char *pszBit = strstr(pszFieldName, "_bit");
1047 160 : long iBit = 0;
1048 160 : char *pszEndPtr = nullptr;
1049 136 : if (pszBit &&
1050 136 : (iBit = strtol(pszBit + strlen("_bit"), &pszEndPtr, 10)) > 0 &&
1051 296 : pszEndPtr && *pszEndPtr == '\0')
1052 : {
1053 136 : std::string osName;
1054 136 : osName.assign(pszFieldName, pszBit - pszFieldName);
1055 136 : if (oSetBitFieldNames.find(osName) == oSetBitFieldNames.end())
1056 : {
1057 268 : if (!osPendingBitFieldName.empty() &&
1058 132 : osPendingBitFieldName != osName)
1059 : {
1060 4 : FlushCreationPendingBitField();
1061 : }
1062 :
1063 136 : if (osPendingBitFieldName.empty())
1064 : {
1065 8 : osPendingBitFieldName = std::move(osName);
1066 8 : nPendingBitFieldSize = 1;
1067 8 : continue;
1068 : }
1069 128 : else if (iBit == nPendingBitFieldSize + 1)
1070 : {
1071 128 : nPendingBitFieldSize++;
1072 128 : continue;
1073 : }
1074 : }
1075 : }
1076 : }
1077 :
1078 157 : FlushCreationPendingBitField();
1079 :
1080 314 : CPLString osTForm;
1081 314 : ColDesc oCol;
1082 157 : oCol.iCol = m_aoColDescs.empty() ? 1 : m_aoColDescs.back().iCol + 1;
1083 157 : oCol.nRepeat = 1;
1084 :
1085 314 : CPLString osRepeat;
1086 157 : const char *pszRepeat = m_aosCreationOptions.FetchNameValue(
1087 314 : (CPLString("REPEAT_") + pszFieldName).c_str());
1088 :
1089 : const auto &osTFormFromMD =
1090 157 : oMapColNameToMetadata[pszFieldName]["TFORM"];
1091 :
1092 : // For fields of type list, determine if we can know if it has a fixed
1093 : // number of elements
1094 157 : if (eType == OFTIntegerList || eType == OFTInteger64List ||
1095 : eType == OFTRealList)
1096 : {
1097 : // First take into account the REPEAT_{FIELD_NAME} creatin option
1098 64 : if (pszRepeat)
1099 : {
1100 1 : osRepeat = pszRepeat;
1101 1 : oCol.nRepeat = atoi(pszRepeat);
1102 : }
1103 : // Then if COMPUTE_REPEAT=AT_FIRST_FEATURE_CREATION was specified
1104 : // and we have a feature, then look at the number of items in the
1105 : // field
1106 78 : else if (bRepeatFromFirstFeature &&
1107 15 : poFeature->IsFieldSetAndNotNull(nFieldIdx))
1108 : {
1109 15 : int nCount = 0;
1110 15 : if (eType == OFTIntegerList)
1111 : {
1112 10 : CPL_IGNORE_RET_VAL(
1113 10 : poFeature->GetFieldAsIntegerList(nFieldIdx, &nCount));
1114 : }
1115 5 : else if (eType == OFTInteger64List)
1116 : {
1117 2 : CPL_IGNORE_RET_VAL(
1118 2 : poFeature->GetFieldAsInteger64List(nFieldIdx, &nCount));
1119 : }
1120 3 : else if (eType == OFTRealList)
1121 : {
1122 3 : CPL_IGNORE_RET_VAL(
1123 3 : poFeature->GetFieldAsDoubleList(nFieldIdx, &nCount));
1124 : }
1125 : else
1126 : {
1127 0 : CPLAssert(false);
1128 : }
1129 15 : osRepeat.Printf("%d", nCount);
1130 15 : oCol.nRepeat = nCount;
1131 : }
1132 : else
1133 : {
1134 64 : if (!osTFormFromMD.empty() && osTFormFromMD[0] >= '1' &&
1135 16 : osTFormFromMD[0] <= '9')
1136 : {
1137 8 : oCol.nRepeat = atoi(osTFormFromMD.c_str());
1138 8 : osRepeat.Printf("%d", oCol.nRepeat);
1139 : }
1140 : else
1141 : {
1142 40 : oCol.nRepeat = 0;
1143 40 : oCol.typechar = 'P';
1144 : }
1145 64 : }
1146 : }
1147 93 : else if (pszRepeat)
1148 : {
1149 0 : CPLError(CE_Warning, CPLE_AppDefined,
1150 : "%s ignored on a non-List data type",
1151 0 : (CPLString("REPEAT_") + pszFieldName).c_str());
1152 : }
1153 :
1154 157 : switch (eType)
1155 : {
1156 64 : case OFTIntegerList:
1157 : case OFTInteger:
1158 64 : if (eSubType == OFSTInt16)
1159 : {
1160 12 : oCol.typechar += 'I';
1161 12 : oCol.nTypeCode = TSHORT;
1162 : }
1163 : else
1164 : {
1165 52 : oCol.typechar += 'J';
1166 52 : oCol.nTypeCode = TINT;
1167 : }
1168 64 : break;
1169 :
1170 16 : case OFTInteger64List:
1171 : case OFTInteger64:
1172 16 : oCol.typechar += 'K';
1173 16 : oCol.nTypeCode = TLONGLONG;
1174 16 : break;
1175 :
1176 49 : case OFTRealList:
1177 : case OFTReal:
1178 49 : if (eSubType == OFSTFloat32)
1179 : {
1180 12 : oCol.typechar += 'E';
1181 12 : oCol.nTypeCode = TFLOAT;
1182 : }
1183 : else
1184 : {
1185 37 : oCol.typechar += 'D';
1186 37 : oCol.nTypeCode = TDOUBLE;
1187 : }
1188 49 : break;
1189 :
1190 28 : case OFTString:
1191 28 : if (osTFormFromMD == "C")
1192 : {
1193 2 : oCol.typechar = "C";
1194 2 : oCol.nTypeCode = TCOMPLEX;
1195 : }
1196 26 : else if (osTFormFromMD == "M")
1197 : {
1198 2 : oCol.typechar = "M";
1199 2 : oCol.nTypeCode = TDBLCOMPLEX;
1200 : }
1201 : else
1202 : {
1203 24 : if (oFieldDefn.GetWidth() == 0)
1204 : {
1205 16 : oCol.typechar = "PA";
1206 : }
1207 : else
1208 : {
1209 8 : oCol.typechar = "A";
1210 8 : oCol.nRepeat = oFieldDefn.GetWidth();
1211 8 : osTForm = CPLSPrintf("%dA", oCol.nRepeat);
1212 : }
1213 24 : oCol.nTypeCode = TSTRING;
1214 : }
1215 28 : break;
1216 :
1217 0 : default:
1218 0 : CPLError(CE_Failure, CPLE_NotSupported,
1219 : "Unsupported field type: should not happen");
1220 0 : break;
1221 : }
1222 :
1223 314 : CPLString osTType(pszFieldName);
1224 157 : if (osTForm.empty())
1225 : {
1226 109 : if ((eType == OFTIntegerList || eType == OFTInteger64List ||
1227 298 : eType == OFTRealList) &&
1228 64 : !osRepeat.empty())
1229 : {
1230 24 : osTForm = osRepeat;
1231 24 : osTForm += oCol.typechar;
1232 : }
1233 : else
1234 : {
1235 125 : osTForm = oCol.typechar;
1236 : }
1237 : }
1238 157 : CPL_IGNORE_RET_VAL(osRepeat);
1239 157 : int status = 0;
1240 157 : fits_insert_col(m_poDS->m_hFITS, oCol.iCol, &osTType[0], &osTForm[0],
1241 : &status);
1242 157 : if (status != 0)
1243 : {
1244 0 : CPLError(CE_Failure, CPLE_AppDefined,
1245 : "fits_insert_col() failed: %d", status);
1246 : }
1247 :
1248 : // Set unit from metadata
1249 471 : auto osUnit = oMapColNameToMetadata[pszFieldName]["TUNIT"];
1250 157 : if (!osUnit.empty())
1251 : {
1252 0 : CPLString osKey;
1253 0 : osKey.Printf("TUNIT%d", oCol.iCol);
1254 0 : fits_update_key_longstr(m_poDS->m_hFITS, osKey.data(),
1255 0 : osUnit.data(), nullptr, &status);
1256 : }
1257 :
1258 157 : m_aoColDescs.emplace_back(oCol);
1259 : }
1260 :
1261 5 : m_anDeferredFieldsIndices.clear();
1262 5 : CPLAssert(static_cast<int>(m_aoColDescs.size()) ==
1263 : m_poFeatureDefn->GetFieldCount());
1264 : }
1265 :
1266 : /************************************************************************/
1267 : /* CreateField() */
1268 : /************************************************************************/
1269 :
1270 313 : OGRErr FITSLayer::CreateField(const OGRFieldDefn *poField, int /* bApproxOK */)
1271 : {
1272 313 : if (!TestCapability(OLCCreateField))
1273 0 : return OGRERR_FAILURE;
1274 313 : if (m_poFeatureDefn->GetFieldIndex(poField->GetNameRef()) >= 0)
1275 : {
1276 0 : CPLError(CE_Failure, CPLE_NotSupported,
1277 : "A field with name %s already exists", poField->GetNameRef());
1278 0 : return OGRERR_FAILURE;
1279 : }
1280 313 : if (poField->GetType() == OFTStringList)
1281 : {
1282 20 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported field type");
1283 20 : return OGRERR_FAILURE;
1284 : }
1285 :
1286 293 : m_anDeferredFieldsIndices.emplace_back(m_poFeatureDefn->GetFieldCount());
1287 293 : m_poFeatureDefn->AddFieldDefn(poField);
1288 293 : return OGRERR_NONE;
1289 : }
1290 :
1291 : /************************************************************************/
1292 : /* WriteCol */
1293 : /************************************************************************/
1294 :
1295 0 : template <class T> static T Round(double dfValue)
1296 : {
1297 0 : return static_cast<T>(floor(dfValue + 0.5));
1298 : }
1299 :
1300 0 : template <> double Round<double>(double dfValue)
1301 : {
1302 0 : return dfValue;
1303 : }
1304 :
1305 0 : template <> float Round<float>(double dfValue)
1306 : {
1307 0 : return static_cast<float>(dfValue);
1308 : }
1309 :
1310 : template <typename T_FITS, typename T_GDAL, int TYPECODE,
1311 : T_GDAL (OGRFeature::*GetFieldMethod)(int) const,
1312 : const T_GDAL *(OGRFeature::*GetFieldListMethod)(int, int *) const>
1313 : struct WriteCol
1314 : {
1315 449 : static int Write(fitsfile *hFITS, const ColDesc &colDesc, int iField,
1316 : LONGLONG irow, const OGRFeature *poFeature)
1317 : {
1318 449 : int status = 0;
1319 449 : int nRepeat = colDesc.nRepeat;
1320 449 : const auto poFieldDefn = poFeature->GetFieldDefnRef(iField);
1321 449 : const auto eOGRType = poFieldDefn->GetType();
1322 449 : int nCount = 0;
1323 : // cppcheck-suppress constStatement
1324 449 : const T_GDAL *panList =
1325 309 : (eOGRType == OFTIntegerList || eOGRType == OFTInteger64List ||
1326 : eOGRType == OFTRealList)
1327 758 : ? (poFeature->*GetFieldListMethod)(iField, &nCount)
1328 : : nullptr;
1329 449 : if (panList)
1330 : {
1331 224 : nRepeat = nRepeat == 0 ? nCount : std::min(nRepeat, nCount);
1332 224 : if (nCount > nRepeat)
1333 : {
1334 8 : CPLError(CE_Warning, CPLE_AppDefined,
1335 : "Field %s of feature " CPL_FRMT_GIB " had %d "
1336 : "elements, but had to be truncated to %d",
1337 : poFieldDefn->GetNameRef(), static_cast<GIntBig>(irow),
1338 : nCount, nRepeat);
1339 : }
1340 : }
1341 : else
1342 : {
1343 225 : nRepeat = 1;
1344 : }
1345 :
1346 449 : if (nRepeat == 0)
1347 32 : return 0;
1348 :
1349 417 : if (colDesc.bHasNull && nRepeat == 1 && poFeature->IsFieldNull(iField))
1350 : {
1351 0 : T_FITS x = static_cast<T_FITS>(colDesc.nNullValue);
1352 0 : fits_write_col(hFITS, TYPECODE, colDesc.iCol, irow, 1, nRepeat, &x,
1353 : &status);
1354 : }
1355 417 : else if (nRepeat == 1)
1356 : {
1357 225 : const auto val =
1358 225 : panList ? panList[0] : (poFeature->*GetFieldMethod)(iField);
1359 225 : if (colDesc.dfScale != 1.0 || colDesc.dfOffset != 0.0)
1360 : {
1361 0 : T_FITS x =
1362 0 : Round<T_FITS>((val - colDesc.dfOffset) / colDesc.dfScale);
1363 0 : fits_write_col(hFITS, TYPECODE, colDesc.iCol, irow, 1, nRepeat,
1364 0 : &x, &status);
1365 : }
1366 : else
1367 : {
1368 225 : T_FITS x = static_cast<T_FITS>(val);
1369 225 : fits_write_col(hFITS, TYPECODE, colDesc.iCol, irow, 1, nRepeat,
1370 : &x, &status);
1371 : }
1372 : }
1373 : else
1374 : {
1375 192 : CPLAssert(panList);
1376 :
1377 384 : std::vector<T_FITS> x;
1378 192 : x.reserve(nRepeat);
1379 192 : if (colDesc.dfScale != 1.0 || colDesc.dfOffset != 0.0)
1380 : {
1381 0 : for (int i = 0; i < nRepeat; i++)
1382 : {
1383 0 : x.push_back(Round<T_FITS>((panList[i] - colDesc.dfOffset) /
1384 0 : colDesc.dfScale));
1385 : }
1386 0 : fits_write_col(hFITS, TYPECODE, colDesc.iCol, irow, 1, nRepeat,
1387 0 : x.data(), &status);
1388 : }
1389 : else
1390 : {
1391 656 : for (int i = 0; i < nRepeat; i++)
1392 : {
1393 464 : x.push_back(static_cast<T_FITS>(panList[i]));
1394 : }
1395 192 : fits_write_col(hFITS, TYPECODE, colDesc.iCol, irow, 1, nRepeat,
1396 : x.data(), &status);
1397 : }
1398 : }
1399 417 : return status;
1400 : }
1401 : };
1402 :
1403 : /************************************************************************/
1404 : /* WriteComplex */
1405 : /************************************************************************/
1406 :
1407 : template <typename T, int TYPECODE> struct WriteComplex
1408 : {
1409 12 : static int Write(fitsfile *hFITS, const ColDesc &colDesc, int iField,
1410 : LONGLONG irow, const OGRFeature *poFeature)
1411 : {
1412 12 : int status = 0;
1413 12 : const auto poFieldDefn = poFeature->GetFieldDefnRef(iField);
1414 12 : if (poFieldDefn->GetType() == OFTStringList)
1415 : {
1416 0 : auto papszStrings = poFeature->GetFieldAsStringList(iField);
1417 0 : const int nCount = CSLCount(papszStrings);
1418 0 : int nRepeat = colDesc.nRepeat;
1419 0 : nRepeat = nRepeat == 0 ? nCount : std::min(nRepeat, nCount);
1420 0 : if (nRepeat > nCount)
1421 : {
1422 0 : CPLError(CE_Warning, CPLE_AppDefined,
1423 : "Field %s of feature " CPL_FRMT_GIB " had %d "
1424 : "elements, but had to be truncated to %d",
1425 : poFieldDefn->GetNameRef(), static_cast<GIntBig>(irow),
1426 : nRepeat, nCount);
1427 : }
1428 0 : std::vector<T> x(2 * nRepeat);
1429 0 : for (int i = 0; i < nRepeat; i++)
1430 : {
1431 0 : double re = 0;
1432 0 : double im = 0;
1433 0 : CPLsscanf(papszStrings[i], "%lf + %lfj", &re, &im);
1434 0 : x[2 * i] = static_cast<T>(re);
1435 0 : x[2 * i + 1] = static_cast<T>(im);
1436 : }
1437 0 : fits_write_col(hFITS, TYPECODE, colDesc.iCol, irow, 1, nRepeat,
1438 : x.data(), &status);
1439 : }
1440 : else
1441 : {
1442 24 : std::vector<T> x(2);
1443 12 : double re = 0;
1444 12 : double im = 0;
1445 12 : CPLsscanf(poFeature->GetFieldAsString(iField), "%lf + %lfj", &re,
1446 : &im);
1447 12 : x[0] = static_cast<T>(re);
1448 12 : x[1] = static_cast<T>(im);
1449 12 : fits_write_col(hFITS, TYPECODE, colDesc.iCol, irow, 1, 1, x.data(),
1450 : &status);
1451 : }
1452 12 : return status;
1453 : }
1454 : };
1455 :
1456 : /************************************************************************/
1457 : /* SetOrCreateFeature() */
1458 : /************************************************************************/
1459 :
1460 14 : bool FITSLayer::SetOrCreateFeature(const OGRFeature *poFeature, LONGLONG nRow)
1461 : {
1462 14 : SetActiveHDU();
1463 :
1464 3631 : const auto WriteField = [this, &poFeature, nRow](int iField)
1465 : {
1466 1023 : const auto poFieldDefn = poFeature->GetFieldDefnRef(iField);
1467 1023 : const auto &colDesc = m_aoColDescs[iField];
1468 : const char typechar =
1469 1836 : (colDesc.typechar[0] == 'P' || colDesc.typechar[0] == 'Q')
1470 1836 : ? colDesc.typechar[1]
1471 813 : : colDesc.typechar[0];
1472 1023 : int nRepeat = colDesc.nRepeat;
1473 1023 : int status = 0;
1474 1023 : if (typechar == 'L')
1475 : {
1476 0 : const auto ToLogical = [](int x) -> char { return x ? '1' : '0'; };
1477 :
1478 0 : if (poFieldDefn->GetType() == OFTIntegerList)
1479 : {
1480 0 : int nCount = 0;
1481 : const int *panVals =
1482 0 : poFeature->GetFieldAsIntegerList(iField, &nCount);
1483 0 : nRepeat = nRepeat == 0 ? nCount : std::min(nRepeat, nCount);
1484 0 : if (nRepeat > nCount)
1485 : {
1486 0 : CPLError(CE_Warning, CPLE_AppDefined,
1487 : "Field %s of feature " CPL_FRMT_GIB " had %d "
1488 : "elements, but had to be truncated to %d",
1489 : poFieldDefn->GetNameRef(),
1490 : static_cast<GIntBig>(nRow), nRepeat, nCount);
1491 : }
1492 0 : std::vector<char> x(nRepeat);
1493 0 : for (int i = 0; i < nRepeat; i++)
1494 : {
1495 0 : x[i] = ToLogical(panVals[i]);
1496 : }
1497 0 : fits_write_col(m_poDS->m_hFITS, TLOGICAL, colDesc.iCol, nRow, 1,
1498 0 : nRepeat, x.data(), &status);
1499 : }
1500 : else
1501 : {
1502 0 : char x = ToLogical(poFeature->GetFieldAsInteger(iField));
1503 0 : fits_write_col(m_poDS->m_hFITS, TLOGICAL, colDesc.iCol, nRow, 1,
1504 : nRepeat, &x, &status);
1505 : }
1506 : }
1507 1023 : else if (typechar == 'X') // bit array
1508 : {
1509 476 : char flag = poFeature->GetFieldAsInteger(iField) ? 0x80 : 0;
1510 476 : fits_write_col_bit(m_poDS->m_hFITS, colDesc.iCol, nRow,
1511 476 : colDesc.iBit, 1, &flag, &status);
1512 : }
1513 547 : else if (typechar == 'B')
1514 : {
1515 0 : if (colDesc.nTypeCode == TSBYTE)
1516 : {
1517 0 : status = WriteCol<
1518 : signed char, int, TSBYTE, &OGRFeature::GetFieldAsInteger,
1519 0 : &OGRFeature::GetFieldAsIntegerList>::Write(m_poDS->m_hFITS,
1520 : colDesc, iField,
1521 : nRow, poFeature);
1522 : }
1523 : else
1524 : {
1525 0 : status = WriteCol<
1526 : GByte, int, TBYTE, &OGRFeature::GetFieldAsInteger,
1527 0 : &OGRFeature::GetFieldAsIntegerList>::Write(m_poDS->m_hFITS,
1528 : colDesc, iField,
1529 : nRow, poFeature);
1530 : }
1531 : }
1532 547 : else if (typechar == 'I')
1533 : {
1534 42 : if (colDesc.nTypeCode == TUSHORT)
1535 : {
1536 0 : status = WriteCol<
1537 : GUInt16, int, TUSHORT, &OGRFeature::GetFieldAsInteger,
1538 0 : &OGRFeature::GetFieldAsIntegerList>::Write(m_poDS->m_hFITS,
1539 : colDesc, iField,
1540 : nRow, poFeature);
1541 : }
1542 : else
1543 : {
1544 42 : status = WriteCol<
1545 : GInt16, int, TSHORT, &OGRFeature::GetFieldAsInteger,
1546 42 : &OGRFeature::GetFieldAsIntegerList>::Write(m_poDS->m_hFITS,
1547 : colDesc, iField,
1548 : nRow, poFeature);
1549 : }
1550 : }
1551 505 : else if (typechar == 'J')
1552 : {
1553 182 : if (colDesc.nTypeCode == TUINT)
1554 : {
1555 0 : status = WriteCol<
1556 : GUInt32, GIntBig, TUINT, &OGRFeature::GetFieldAsInteger64,
1557 0 : &OGRFeature::GetFieldAsInteger64List>::Write(m_poDS
1558 : ->m_hFITS,
1559 : colDesc,
1560 : iField, nRow,
1561 : poFeature);
1562 : }
1563 : else
1564 : {
1565 182 : status = WriteCol<
1566 : GInt32, int, TINT, &OGRFeature::GetFieldAsInteger,
1567 182 : &OGRFeature::GetFieldAsIntegerList>::Write(m_poDS->m_hFITS,
1568 : colDesc, iField,
1569 : nRow, poFeature);
1570 : }
1571 : }
1572 323 : else if (typechar == 'K')
1573 : {
1574 56 : status = WriteCol<
1575 : GInt64, GIntBig, TLONGLONG, &OGRFeature::GetFieldAsInteger64,
1576 56 : &OGRFeature::GetFieldAsInteger64List>::Write(m_poDS->m_hFITS,
1577 : colDesc, iField,
1578 : nRow, poFeature);
1579 : }
1580 267 : else if (typechar == 'A') // Character
1581 : {
1582 86 : if (poFieldDefn->GetType() == OFTStringList)
1583 : {
1584 0 : auto papszStrings = poFeature->GetFieldAsStringList(iField);
1585 0 : const int nStringCount = CSLCount(papszStrings);
1586 0 : const int nItems = std::min(colDesc.nItems, nStringCount);
1587 0 : if (nItems > nStringCount)
1588 : {
1589 0 : CPLError(CE_Warning, CPLE_AppDefined,
1590 : "Field %s of feature " CPL_FRMT_GIB " had %d "
1591 : "elements, but had to be truncated to %d",
1592 : poFieldDefn->GetNameRef(),
1593 : static_cast<GIntBig>(nRow), nItems, nStringCount);
1594 : }
1595 0 : fits_write_col_str(m_poDS->m_hFITS, colDesc.iCol, nRow, 1,
1596 : nItems, papszStrings, &status);
1597 : }
1598 : else
1599 : {
1600 : char *pszStr =
1601 86 : const_cast<char *>(poFeature->GetFieldAsString(iField));
1602 86 : fits_write_col_str(m_poDS->m_hFITS, colDesc.iCol, nRow, 1, 1,
1603 : &pszStr, &status);
1604 : }
1605 : }
1606 181 : else if (typechar == 'E') // IEEE754 32bit
1607 : {
1608 42 : status = WriteCol<
1609 : float, double, TFLOAT, &OGRFeature::GetFieldAsDouble,
1610 42 : &OGRFeature::GetFieldAsDoubleList>::Write(m_poDS->m_hFITS,
1611 : colDesc, iField, nRow,
1612 : poFeature);
1613 : }
1614 139 : else if (typechar == 'D') // IEEE754 64bit
1615 : {
1616 127 : status = WriteCol<
1617 : double, double, TDOUBLE, &OGRFeature::GetFieldAsDouble,
1618 127 : &OGRFeature::GetFieldAsDoubleList>::Write(m_poDS->m_hFITS,
1619 : colDesc, iField, nRow,
1620 : poFeature);
1621 : }
1622 12 : else if (typechar == 'C') // IEEE754 32bit complex
1623 : {
1624 6 : status = WriteComplex<float, TCOMPLEX>::Write(
1625 6 : m_poDS->m_hFITS, colDesc, iField, nRow, poFeature);
1626 : }
1627 6 : else if (typechar == 'M') // IEEE754 64bit complex
1628 : {
1629 6 : status = WriteComplex<double, TDBLCOMPLEX>::Write(
1630 6 : m_poDS->m_hFITS, colDesc, iField, nRow, poFeature);
1631 : }
1632 : else
1633 : {
1634 0 : CPLDebug("FITS", "Unhandled typechar %c", typechar);
1635 : }
1636 1023 : if (status)
1637 : {
1638 0 : CPLError(CE_Failure, CPLE_AppDefined, "fits_write_col() failed");
1639 : }
1640 1023 : return status == 0;
1641 14 : };
1642 :
1643 14 : bool bOK = true;
1644 14 : const int nFieldCount = poFeature->GetFieldCount();
1645 1037 : for (int iField = 0; iField < nFieldCount; iField++)
1646 : {
1647 1023 : if (!WriteField(iField))
1648 0 : bOK = false;
1649 : }
1650 14 : return bOK;
1651 : }
1652 :
1653 : /************************************************************************/
1654 : /* ICreateFeature() */
1655 : /************************************************************************/
1656 :
1657 13 : OGRErr FITSLayer::ICreateFeature(OGRFeature *poFeature)
1658 : {
1659 13 : if (!TestCapability(OLCSequentialWrite))
1660 0 : return OGRERR_FAILURE;
1661 :
1662 13 : RunDeferredFieldCreation(poFeature);
1663 :
1664 13 : m_nRows++;
1665 13 : SetActiveHDU();
1666 13 : const bool bOK = SetOrCreateFeature(poFeature, m_nRows);
1667 13 : poFeature->SetFID(m_nRows);
1668 :
1669 13 : return bOK ? OGRERR_NONE : OGRERR_FAILURE;
1670 : }
1671 :
1672 : /************************************************************************/
1673 : /* ISetFeature() */
1674 : /************************************************************************/
1675 :
1676 3 : OGRErr FITSLayer::ISetFeature(OGRFeature *poFeature)
1677 : {
1678 3 : if (!TestCapability(OLCRandomWrite))
1679 0 : return OGRERR_FAILURE;
1680 :
1681 3 : RunDeferredFieldCreation();
1682 :
1683 3 : const GIntBig nRow = poFeature->GetFID();
1684 3 : if (nRow <= 0 || nRow > m_nRows)
1685 2 : return OGRERR_NON_EXISTING_FEATURE;
1686 :
1687 1 : SetActiveHDU();
1688 1 : const bool bOK = SetOrCreateFeature(poFeature, nRow);
1689 1 : return bOK ? OGRERR_NONE : OGRERR_FAILURE;
1690 : }
1691 :
1692 : /************************************************************************/
1693 : /* DeleteFeature() */
1694 : /************************************************************************/
1695 :
1696 3 : OGRErr FITSLayer::DeleteFeature(GIntBig nFID)
1697 : {
1698 3 : if (!TestCapability(OLCDeleteFeature))
1699 0 : return OGRERR_FAILURE;
1700 :
1701 3 : if (nFID <= 0 || nFID > m_nRows)
1702 2 : return OGRERR_NON_EXISTING_FEATURE;
1703 :
1704 1 : SetActiveHDU();
1705 :
1706 1 : int status = 0;
1707 1 : fits_delete_rows(m_poDS->m_hFITS, nFID, 1, &status);
1708 1 : m_nRows--;
1709 1 : return status == 0 ? OGRERR_NONE : OGRERR_FAILURE;
1710 : }
1711 :
1712 : /************************************************************************/
1713 : /* FITSRasterBand() */
1714 : /************************************************************************/
1715 :
1716 133 : FITSRasterBand::FITSRasterBand(FITSDataset *poDSIn, int nBandIn)
1717 133 : : m_poFDS(poDSIn)
1718 : {
1719 133 : poDS = poDSIn;
1720 133 : nBand = nBandIn;
1721 133 : eDataType = poDSIn->m_gdalDataType;
1722 133 : nBlockXSize = poDSIn->nRasterXSize;
1723 133 : nBlockYSize = 1;
1724 133 : }
1725 :
1726 : /************************************************************************/
1727 : /* ~FITSRasterBand() */
1728 : /************************************************************************/
1729 :
1730 266 : FITSRasterBand::~FITSRasterBand()
1731 : {
1732 133 : FlushCache(true);
1733 266 : }
1734 :
1735 : /************************************************************************/
1736 : /* IReadBlock() */
1737 : /************************************************************************/
1738 :
1739 160 : CPLErr FITSRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
1740 : void *pImage)
1741 : {
1742 : // A FITS block is one row (we assume BSQ formatted data)
1743 160 : FITSDataset *dataset = m_poFDS;
1744 160 : fitsfile *hFITS = dataset->m_hFITS;
1745 160 : int status = 0;
1746 :
1747 : // Since a FITS block is a whole row, nBlockXOff must be zero
1748 : // and the row number equals the y block offset. Also, nBlockYOff
1749 : // cannot be greater than the number of rows
1750 160 : CPLAssert(nBlockXOff == 0);
1751 160 : CPLAssert(nBlockYOff < nRasterYSize);
1752 :
1753 : // Calculate offsets and read in the data. Note that FITS array offsets
1754 : // start at 1 at the bottom left...
1755 160 : LONGLONG offset =
1756 160 : static_cast<LONGLONG>(nBand - 1) * nRasterXSize * nRasterYSize +
1757 160 : (static_cast<LONGLONG>(nRasterYSize - 1 - nBlockYOff) * nRasterXSize +
1758 : 1);
1759 160 : long nElements = nRasterXSize;
1760 :
1761 : // If we haven't written this block to the file yet, then attempting
1762 : // to read causes an error, so in this case, just return zeros.
1763 160 : if (!dataset->m_isExistingFile && offset > dataset->m_highestOffsetWritten)
1764 : {
1765 0 : memset(pImage, 0,
1766 0 : nBlockXSize * nBlockYSize * GDALGetDataTypeSize(eDataType) / 8);
1767 0 : return CE_None;
1768 : }
1769 :
1770 : // Otherwise read in the image data
1771 160 : fits_read_img(hFITS, dataset->m_fitsDataType, offset, nElements, nullptr,
1772 : pImage, nullptr, &status);
1773 :
1774 : // Capture special case of non-zero status due to data range
1775 : // overflow Standard GDAL policy is to silently truncate, which is
1776 : // what CFITSIO does, in addition to returning NUM_OVERFLOW (412) as
1777 : // the status.
1778 160 : if (status == NUM_OVERFLOW)
1779 0 : status = 0;
1780 :
1781 160 : if (status)
1782 : {
1783 0 : CPLError(CE_Failure, CPLE_AppDefined,
1784 : "Couldn't read image data from FITS file (%d).", status);
1785 0 : return CE_Failure;
1786 : }
1787 :
1788 160 : return CE_None;
1789 : }
1790 :
1791 : /************************************************************************/
1792 : /* IWriteBlock() */
1793 : /* */
1794 : /************************************************************************/
1795 :
1796 450 : CPLErr FITSRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
1797 : void *pImage)
1798 : {
1799 450 : FITSDataset *dataset = m_poFDS;
1800 450 : fitsfile *hFITS = dataset->m_hFITS;
1801 450 : int status = 0;
1802 :
1803 : // Since a FITS block is a whole row, nBlockXOff must be zero
1804 : // and the row number equals the y block offset. Also, nBlockYOff
1805 : // cannot be greater than the number of rows
1806 :
1807 : // Calculate offsets and read in the data. Note that FITS array offsets
1808 : // start at 1 at the bottom left...
1809 450 : LONGLONG offset =
1810 450 : static_cast<LONGLONG>(nBand - 1) * nRasterXSize * nRasterYSize +
1811 450 : (static_cast<LONGLONG>(nRasterYSize - 1 - nBlockYOff) * nRasterXSize +
1812 : 1);
1813 450 : long nElements = nRasterXSize;
1814 450 : fits_write_img(hFITS, dataset->m_fitsDataType, offset, nElements, pImage,
1815 : &status);
1816 :
1817 : // Capture special case of non-zero status due to data range
1818 : // overflow Standard GDAL policy is to silently truncate, which is
1819 : // what CFITSIO does, in addition to returning NUM_OVERFLOW (412) as
1820 : // the status.
1821 450 : if (status == NUM_OVERFLOW)
1822 0 : status = 0;
1823 :
1824 : // Check for other errors
1825 450 : if (status)
1826 : {
1827 0 : CPLError(CE_Failure, CPLE_AppDefined,
1828 : "Error writing image data to FITS file (%d).", status);
1829 0 : return CE_Failure;
1830 : }
1831 :
1832 : // When we write a block, update the offset counter that we've written
1833 450 : if (offset > dataset->m_highestOffsetWritten)
1834 33 : dataset->m_highestOffsetWritten = offset;
1835 :
1836 450 : return CE_None;
1837 : }
1838 :
1839 : /************************************************************************/
1840 : /* ==================================================================== */
1841 : /* FITSDataset */
1842 : /* ==================================================================== */
1843 : /************************************************************************/
1844 :
1845 : // Some useful utility functions
1846 :
1847 : // Simple static function to determine if FITS header keyword should
1848 : // be saved in meta data.
1849 : static const char *const ignorableFITSHeaders[] = {
1850 : "SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "END",
1851 : "XTENSION", "PCOUNT", "GCOUNT", "EXTEND", "CONTINUE", "COMMENT", "",
1852 : "LONGSTRN", "BZERO", "BSCALE", "BLANK", "CHECKSUM", "DATASUM",
1853 : };
1854 :
1855 2269 : static bool isIgnorableFITSHeader(const char *name)
1856 : {
1857 39230 : for (const char *keyword : ignorableFITSHeaders)
1858 : {
1859 37667 : if (strcmp(name, keyword) == 0)
1860 706 : return true;
1861 : }
1862 1563 : return false;
1863 : }
1864 :
1865 : /************************************************************************/
1866 : /* FITSDataset() */
1867 : /************************************************************************/
1868 :
1869 103 : FITSDataset::FITSDataset()
1870 : {
1871 103 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1872 103 : m_adfGeoTransform[0] = 0;
1873 103 : m_adfGeoTransform[1] = 1;
1874 103 : m_adfGeoTransform[2] = 0;
1875 103 : m_adfGeoTransform[3] = 0;
1876 103 : m_adfGeoTransform[4] = 0;
1877 103 : m_adfGeoTransform[5] = 1;
1878 103 : }
1879 :
1880 : /************************************************************************/
1881 : /* ~FITSDataset() */
1882 : /************************************************************************/
1883 :
1884 206 : FITSDataset::~FITSDataset()
1885 : {
1886 :
1887 103 : int status = 0;
1888 103 : if (m_hFITS)
1889 : {
1890 103 : m_apoLayers.clear();
1891 :
1892 103 : if (m_hduNum > 0 && eAccess == GA_Update)
1893 : {
1894 : // Only do this if we've successfully opened the file and update
1895 : // capability. Write any meta data to the file that's compatible
1896 : // with FITS.
1897 41 : fits_movabs_hdu(m_hFITS, m_hduNum, nullptr, &status);
1898 41 : fits_write_key_longwarn(m_hFITS, &status);
1899 41 : if (status)
1900 : {
1901 0 : CPLError(CE_Warning, CPLE_AppDefined,
1902 : "Couldn't move to HDU %d in FITS file %s (%d).\n",
1903 : m_hduNum, GetDescription(), status);
1904 : }
1905 41 : char **metaData = FITSDataset::GetMetadata();
1906 41 : int count = CSLCount(metaData);
1907 65 : for (int i = 0; i < count; ++i)
1908 : {
1909 24 : const char *field = CSLGetField(metaData, i);
1910 24 : if (strlen(field) == 0)
1911 0 : continue;
1912 : else
1913 : {
1914 24 : char *key = nullptr;
1915 24 : const char *value = CPLParseNameValue(field, &key);
1916 : // FITS keys must be less than 8 chars
1917 26 : if (key != nullptr && strlen(key) <= 8 &&
1918 2 : !isIgnorableFITSHeader(key))
1919 : {
1920 : // Although FITS provides support for different value
1921 : // types, the GDAL Metadata mechanism works only with
1922 : // string values. Prior to about 2003-05-02, this driver
1923 : // would attempt to guess the value type from the
1924 : // metadata value string amd then would use the
1925 : // appropriate type-specific FITS keyword update
1926 : // routine. This was found to be troublesome (e.g. a
1927 : // numeric version string with leading zeros would be
1928 : // interpreted as a number and might get those leading
1929 : // zeros stripped), and so now the driver writes every
1930 : // value as a string. In practice this is not a problem
1931 : // since most FITS reading routines will convert from
1932 : // strings to numbers automatically, but if you want
1933 : // finer control, use the underlying FITS handle. Note:
1934 : // to avoid a compiler warning we copy the const value
1935 : // string to a non const one.
1936 2 : char *valueCpy = CPLStrdup(value);
1937 2 : fits_update_key_longstr(m_hFITS, key, valueCpy, nullptr,
1938 : &status);
1939 2 : CPLFree(valueCpy);
1940 :
1941 : // Check for errors.
1942 2 : if (status)
1943 : {
1944 : // Throw a warning with CFITSIO error status, then
1945 : // ignore status
1946 0 : CPLError(
1947 : CE_Warning, CPLE_AppDefined,
1948 : "Couldn't update key %s in FITS file %s (%d).",
1949 : key, GetDescription(), status);
1950 0 : status = 0;
1951 0 : return;
1952 : }
1953 : }
1954 : // Must free up key
1955 24 : CPLFree(key);
1956 : }
1957 : }
1958 :
1959 : // Writing nodata value
1960 41 : if (m_gdalDataType != GDT_Float32 && m_gdalDataType != GDT_Float64)
1961 : {
1962 33 : fits_update_key(m_hFITS, TDOUBLE, "BLANK", &m_dfNoDataValue,
1963 : nullptr, &status);
1964 33 : if (status)
1965 : {
1966 : // Throw a warning with CFITSIO error status, then ignore
1967 : // status
1968 0 : CPLError(CE_Warning, CPLE_AppDefined,
1969 : "Couldn't update key BLANK in FITS file %s (%d).",
1970 : GetDescription(), status);
1971 0 : status = 0;
1972 0 : return;
1973 : }
1974 : }
1975 :
1976 : // Writing Scale and offset if defined
1977 : int pbSuccess;
1978 41 : GDALRasterBand *poSrcBand = GDALPamDataset::GetRasterBand(1);
1979 41 : double dfScale = poSrcBand->GetScale(&pbSuccess);
1980 41 : double dfOffset = poSrcBand->GetOffset(&pbSuccess);
1981 41 : if (m_bMetadataChanged)
1982 : {
1983 1 : fits_update_key(m_hFITS, TDOUBLE, "BSCALE", &dfScale, nullptr,
1984 : &status);
1985 1 : if (status)
1986 : {
1987 : // Throw a warning with CFITSIO error status, then ignore
1988 : // status
1989 0 : CPLError(CE_Warning, CPLE_AppDefined,
1990 : "Couldn't update key BSCALE in FITS file %s (%d).",
1991 : GetDescription(), status);
1992 0 : status = 0;
1993 0 : return;
1994 : }
1995 1 : fits_update_key(m_hFITS, TDOUBLE, "BZERO", &dfOffset, nullptr,
1996 : &status);
1997 1 : if (status)
1998 : {
1999 : // Throw a warning with CFITSIO error status, then ignore
2000 : // status
2001 0 : CPLError(CE_Warning, CPLE_AppDefined,
2002 : "Couldn't update key BZERO in FITS file %s (%d).",
2003 : GetDescription(), status);
2004 0 : status = 0;
2005 0 : return;
2006 : }
2007 : }
2008 :
2009 : // Copy georeferencing info to PAM if the profile is not FITS
2010 41 : GDALPamDataset::SetSpatialRef(GDALPamDataset::GetSpatialRef());
2011 :
2012 : // Write geographic info
2013 41 : if (m_bFITSInfoChanged)
2014 : {
2015 40 : WriteFITSInfo();
2016 : }
2017 :
2018 : // Make sure we flush the raster cache before we close the file!
2019 41 : FlushCache(true);
2020 : }
2021 :
2022 : // Close the FITS handle
2023 103 : fits_close_file(m_hFITS, &status);
2024 103 : if (status != 0)
2025 : {
2026 0 : CPLError(CE_Failure, CPLE_AppDefined,
2027 : "fits_close_file() failed with %d", status);
2028 : }
2029 : }
2030 206 : }
2031 :
2032 : /************************************************************************/
2033 : /* GetRawBinaryLayout() */
2034 : /************************************************************************/
2035 :
2036 2 : bool FITSDataset::GetRawBinaryLayout(GDALDataset::RawBinaryLayout &sLayout)
2037 : {
2038 2 : if (m_hduNum == 0)
2039 0 : return false;
2040 2 : int status = 0;
2041 2 : if (fits_is_compressed_image(m_hFITS, &status))
2042 0 : return false;
2043 2 : GDALDataType eDT = GetRasterBand(1)->GetRasterDataType();
2044 2 : if (eDT == GDT_UInt16 || eDT == GDT_UInt32)
2045 0 : return false; // are supported as native signed with offset
2046 :
2047 2 : sLayout.osRawFilename = GetDescription();
2048 : static_assert(sizeof(OFF_T) == 8, "OFF_T should be 64 bits !");
2049 2 : OFF_T headerstart = 0;
2050 2 : OFF_T datastart = 0;
2051 2 : OFF_T dataend = 0;
2052 2 : fits_get_hduoff(m_hFITS, &headerstart, &datastart, &dataend, &status);
2053 2 : if (nBands > 1)
2054 0 : sLayout.eInterleaving = RawBinaryLayout::Interleaving::BSQ;
2055 2 : sLayout.eDataType = eDT;
2056 2 : sLayout.bLittleEndianOrder = false;
2057 2 : sLayout.nImageOffset = static_cast<GIntBig>(datastart);
2058 2 : sLayout.nPixelOffset = GDALGetDataTypeSizeBytes(eDT);
2059 2 : sLayout.nLineOffset = sLayout.nPixelOffset * nRasterXSize;
2060 2 : sLayout.nBandOffset = sLayout.nLineOffset * nRasterYSize;
2061 2 : return true;
2062 : }
2063 :
2064 : /************************************************************************/
2065 : /* Init() */
2066 : /************************************************************************/
2067 :
2068 80 : CPLErr FITSDataset::Init(fitsfile *hFITS, bool isExistingFile, int hduNum)
2069 : {
2070 :
2071 80 : m_hFITS = hFITS;
2072 80 : m_isExistingFile = isExistingFile;
2073 :
2074 80 : int status = 0;
2075 : double offset;
2076 :
2077 80 : int hduType = 0;
2078 80 : fits_movabs_hdu(hFITS, hduNum, &hduType, &status);
2079 80 : if (status)
2080 : {
2081 1 : CPLError(CE_Failure, CPLE_AppDefined,
2082 : "Couldn't move to HDU %d in FITS file %s (%d).", hduNum,
2083 1 : GetDescription(), status);
2084 1 : return CE_Failure;
2085 : }
2086 :
2087 79 : if (hduType != IMAGE_HDU)
2088 : {
2089 0 : CPLError(CE_Failure, CPLE_AppDefined, "HDU %d is not an image.",
2090 : hduNum);
2091 0 : return CE_Failure;
2092 : }
2093 :
2094 : // Get the image info for this dataset (note that all bands in a FITS
2095 : // dataset have the same type)
2096 79 : int bitpix = 0;
2097 79 : int naxis = 0;
2098 79 : const int maxdim = 3;
2099 79 : long naxes[maxdim] = {0, 0, 0};
2100 79 : fits_get_img_param(hFITS, maxdim, &bitpix, &naxis, naxes, &status);
2101 79 : if (status)
2102 : {
2103 0 : CPLError(CE_Failure, CPLE_AppDefined,
2104 : "Couldn't determine image parameters of FITS file %s (%d)",
2105 0 : GetDescription(), status);
2106 0 : return CE_Failure;
2107 : }
2108 :
2109 79 : m_hduNum = hduNum;
2110 :
2111 79 : fits_read_key(hFITS, TDOUBLE, "BZERO", &offset, nullptr, &status);
2112 79 : if (status)
2113 : {
2114 : // BZERO is not mandatory offset defaulted to 0 if BZERO is missing
2115 63 : status = 0;
2116 63 : offset = 0.;
2117 : }
2118 :
2119 79 : fits_read_key(hFITS, TDOUBLE, "BLANK", &m_dfNoDataValue, nullptr, &status);
2120 79 : m_bNoDataSet = !status;
2121 79 : status = 0;
2122 :
2123 : // Determine data type and nodata value if BLANK keyword is absent
2124 79 : if (bitpix == BYTE_IMG)
2125 : {
2126 33 : m_gdalDataType = GDT_Byte;
2127 33 : m_fitsDataType = TBYTE;
2128 : }
2129 46 : else if (bitpix == SHORT_IMG)
2130 : {
2131 18 : if (offset == 32768.)
2132 : {
2133 7 : m_gdalDataType = GDT_UInt16;
2134 7 : m_fitsDataType = TUSHORT;
2135 : }
2136 : else
2137 : {
2138 11 : m_gdalDataType = GDT_Int16;
2139 11 : m_fitsDataType = TSHORT;
2140 : }
2141 : }
2142 28 : else if (bitpix == LONG_IMG)
2143 : {
2144 14 : if (offset == 2147483648.)
2145 : {
2146 7 : m_gdalDataType = GDT_UInt32;
2147 7 : m_fitsDataType = TUINT;
2148 : }
2149 : else
2150 : {
2151 7 : m_gdalDataType = GDT_Int32;
2152 7 : m_fitsDataType = TINT;
2153 : }
2154 : }
2155 14 : else if (bitpix == FLOAT_IMG)
2156 : {
2157 7 : m_gdalDataType = GDT_Float32;
2158 7 : m_fitsDataType = TFLOAT;
2159 : }
2160 7 : else if (bitpix == DOUBLE_IMG)
2161 : {
2162 7 : m_gdalDataType = GDT_Float64;
2163 7 : m_fitsDataType = TDOUBLE;
2164 : }
2165 : else
2166 : {
2167 0 : CPLError(CE_Failure, CPLE_AppDefined,
2168 0 : "FITS file %s has unknown data type: %d.", GetDescription(),
2169 : bitpix);
2170 0 : return CE_Failure;
2171 : }
2172 :
2173 : // Determine image dimensions - we assume BSQ ordering
2174 79 : if (naxis == 2)
2175 : {
2176 55 : nRasterXSize = static_cast<int>(naxes[0]);
2177 55 : nRasterYSize = static_cast<int>(naxes[1]);
2178 55 : nBands = 1;
2179 : }
2180 24 : else if (naxis == 3)
2181 : {
2182 24 : nRasterXSize = static_cast<int>(naxes[0]);
2183 24 : nRasterYSize = static_cast<int>(naxes[1]);
2184 24 : nBands = static_cast<int>(naxes[2]);
2185 : }
2186 : else
2187 : {
2188 0 : CPLError(CE_Failure, CPLE_AppDefined,
2189 : "FITS file %s does not have 2 or 3 dimensions.",
2190 0 : GetDescription());
2191 0 : return CE_Failure;
2192 : }
2193 :
2194 : // Create the bands
2195 212 : for (int i = 0; i < nBands; ++i)
2196 133 : SetBand(i + 1, new FITSRasterBand(this, i + 1));
2197 :
2198 79 : return CE_None;
2199 : }
2200 :
2201 : /************************************************************************/
2202 : /* LoadMetadata() */
2203 : /************************************************************************/
2204 :
2205 62 : void FITSDataset::LoadMetadata(GDALMajorObject *poTarget)
2206 : {
2207 : // Read header information from file and use it to set metadata
2208 : // This process understands the CONTINUE standard for long strings.
2209 : // We don't bother to capture header names that duplicate information
2210 : // already captured elsewhere (e.g. image dimensions and type)
2211 : int keyNum;
2212 : char key[100];
2213 : char value[100];
2214 62 : CPLStringList aosMD;
2215 :
2216 62 : int nKeys = 0;
2217 62 : int nMoreKeys = 0;
2218 62 : int status = 0;
2219 62 : fits_get_hdrspace(m_hFITS, &nKeys, &nMoreKeys, &status);
2220 2329 : for (keyNum = 1; keyNum <= nKeys; keyNum++)
2221 : {
2222 2267 : fits_read_keyn(m_hFITS, keyNum, key, value, nullptr, &status);
2223 2267 : if (status)
2224 : {
2225 0 : CPLError(CE_Failure, CPLE_AppDefined,
2226 : "Error while reading key %d from FITS file %s (%d)",
2227 0 : keyNum, GetDescription(), status);
2228 0 : return;
2229 : }
2230 2267 : if (strcmp(key, "END") == 0)
2231 : {
2232 : // We should not get here in principle since the END
2233 : // keyword shouldn't be counted in nKeys, but who knows.
2234 0 : break;
2235 : }
2236 2267 : else if (isIgnorableFITSHeader(key))
2237 : {
2238 : // Ignore it
2239 : }
2240 : else
2241 : { // Going to store something, but check for long strings etc
2242 : // Strip off leading and trailing quote if present
2243 1561 : char *newValue = value;
2244 1561 : if (value[0] == '\'' && value[strlen(value) - 1] == '\'')
2245 : {
2246 996 : newValue = value + 1;
2247 996 : value[strlen(value) - 1] = '\0';
2248 : }
2249 : // Check for long string
2250 1561 : if (strrchr(newValue, '&') == newValue + strlen(newValue) - 1)
2251 : {
2252 : // Value string ends in "&", so use long string conventions
2253 0 : char *longString = nullptr;
2254 0 : fits_read_key_longstr(m_hFITS, key, &longString, nullptr,
2255 : &status);
2256 : // Note that read_key_longstr already strips quotes
2257 0 : if (status)
2258 : {
2259 0 : CPLError(CE_Failure, CPLE_AppDefined,
2260 : "Error while reading long string for key %s from "
2261 : "FITS file %s (%d)",
2262 0 : key, GetDescription(), status);
2263 0 : return;
2264 : }
2265 0 : poTarget->SetMetadataItem(key, longString);
2266 0 : free(longString);
2267 : }
2268 : else
2269 : { // Normal keyword
2270 1561 : poTarget->SetMetadataItem(key, newValue);
2271 : }
2272 : }
2273 : }
2274 : }
2275 :
2276 : /************************************************************************/
2277 : /* GetMetadata() */
2278 : /************************************************************************/
2279 :
2280 70 : char **FITSDataset::GetMetadata(const char *pszDomain)
2281 :
2282 : {
2283 70 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
2284 : {
2285 8 : return m_aosSubdatasets.List();
2286 : }
2287 :
2288 62 : return GDALPamDataset::GetMetadata(pszDomain);
2289 : }
2290 :
2291 : /************************************************************************/
2292 : /* GetLayer() */
2293 : /************************************************************************/
2294 :
2295 15 : OGRLayer *FITSDataset::GetLayer(int idx)
2296 : {
2297 15 : if (idx < 0 || idx >= GetLayerCount())
2298 2 : return nullptr;
2299 13 : return m_apoLayers[idx].get();
2300 : }
2301 :
2302 : /************************************************************************/
2303 : /* ICreateLayer() */
2304 : /************************************************************************/
2305 :
2306 4 : OGRLayer *FITSDataset::ICreateLayer(const char *pszName,
2307 : const OGRGeomFieldDefn *poGeomFieldDefn,
2308 : CSLConstList papszOptions)
2309 : {
2310 4 : if (!TestCapability(ODsCCreateLayer))
2311 0 : return nullptr;
2312 :
2313 4 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
2314 4 : if (eGType != wkbNone)
2315 : {
2316 0 : CPLError(CE_Failure, CPLE_NotSupported, "Spatial tables not supported");
2317 0 : return nullptr;
2318 : }
2319 :
2320 4 : int status = 0;
2321 4 : int numHDUs = 0;
2322 4 : fits_get_num_hdus(m_hFITS, &numHDUs, &status);
2323 4 : if (status != 0)
2324 : {
2325 0 : CPLError(CE_Failure, CPLE_AppDefined, "fits_get_num_hdus() failed: %d",
2326 : status);
2327 0 : return nullptr;
2328 : }
2329 :
2330 4 : fits_create_tbl(m_hFITS, BINARY_TBL,
2331 : 0, // number of initial rows
2332 : 0, // nfields,
2333 : nullptr, // ttype,
2334 : nullptr, // tform
2335 : nullptr, // tunits
2336 : pszName, &status);
2337 4 : if (status != 0)
2338 : {
2339 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot create layer");
2340 0 : return nullptr;
2341 : }
2342 :
2343 : // If calling fits_get_num_hdus() here on a freshly new created file,
2344 : // it reports only one HDU, missing the initial dummy HDU
2345 4 : if (numHDUs == 0)
2346 : {
2347 4 : numHDUs = 2;
2348 : }
2349 : else
2350 : {
2351 0 : numHDUs++;
2352 : }
2353 :
2354 4 : auto poLayer = new FITSLayer(this, numHDUs, pszName);
2355 4 : poLayer->SetCreationOptions(papszOptions);
2356 4 : m_apoLayers.emplace_back(std::unique_ptr<FITSLayer>(poLayer));
2357 4 : return m_apoLayers.back().get();
2358 : }
2359 :
2360 : /************************************************************************/
2361 : /* TestCapability() */
2362 : /************************************************************************/
2363 :
2364 20 : int FITSDataset::TestCapability(const char *pszCap)
2365 : {
2366 20 : if (EQUAL(pszCap, ODsCCreateLayer))
2367 8 : return eAccess == GA_Update;
2368 12 : return false;
2369 : }
2370 :
2371 : /************************************************************************/
2372 : /* Open() */
2373 : /************************************************************************/
2374 :
2375 63 : GDALDataset *FITSDataset::Open(GDALOpenInfo *poOpenInfo)
2376 : {
2377 :
2378 63 : if (!FITSDriverIdentify(poOpenInfo))
2379 0 : return nullptr;
2380 :
2381 126 : CPLString osFilename(poOpenInfo->pszFilename);
2382 63 : int iSelectedHDU = 0;
2383 63 : if (STARTS_WITH(poOpenInfo->pszFilename, "FITS:"))
2384 : {
2385 : const CPLStringList aosTokens(
2386 9 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
2387 9 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
2388 9 : if (aosTokens.size() != 3)
2389 : {
2390 2 : return nullptr;
2391 : }
2392 7 : osFilename = aosTokens[1];
2393 7 : iSelectedHDU = atoi(aosTokens[2]);
2394 7 : if (iSelectedHDU <= 0)
2395 : {
2396 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid HDU number");
2397 1 : return nullptr;
2398 : }
2399 : }
2400 :
2401 : // Get access mode and attempt to open the file
2402 60 : int status = 0;
2403 60 : fitsfile *hFITS = nullptr;
2404 60 : if (poOpenInfo->eAccess == GA_ReadOnly)
2405 58 : fits_open_file(&hFITS, osFilename.c_str(), READONLY, &status);
2406 : else
2407 2 : fits_open_file(&hFITS, osFilename.c_str(), READWRITE, &status);
2408 60 : if (status)
2409 : {
2410 1 : CPLError(CE_Failure, CPLE_AppDefined,
2411 : "Error while opening FITS file %s (%d).\n", osFilename.c_str(),
2412 : status);
2413 1 : fits_close_file(hFITS, &status);
2414 1 : return nullptr;
2415 : }
2416 : // Create a FITSDataset object
2417 118 : auto dataset = std::make_unique<FITSDataset>();
2418 59 : dataset->m_isExistingFile = true;
2419 59 : dataset->m_hFITS = hFITS;
2420 59 : dataset->eAccess = poOpenInfo->eAccess;
2421 59 : dataset->SetPhysicalFilename(osFilename);
2422 :
2423 : /* -------------------------------------------------------------------- */
2424 : /* Iterate over HDUs */
2425 : /* -------------------------------------------------------------------- */
2426 59 : bool firstHDUIsDummy = false;
2427 59 : int firstValidHDU = 0;
2428 118 : CPLStringList aosSubdatasets;
2429 59 : bool hasVector = false;
2430 59 : if (iSelectedHDU == 0)
2431 : {
2432 54 : int numHDUs = 0;
2433 54 : fits_get_num_hdus(hFITS, &numHDUs, &status);
2434 54 : if (numHDUs <= 0)
2435 : {
2436 0 : return nullptr;
2437 : }
2438 :
2439 131 : for (int iHDU = 1; iHDU <= numHDUs; iHDU++)
2440 : {
2441 77 : int hduType = 0;
2442 77 : fits_movabs_hdu(hFITS, iHDU, &hduType, &status);
2443 77 : if (status)
2444 : {
2445 31 : continue;
2446 : }
2447 :
2448 77 : char szExtname[81] = {0};
2449 77 : fits_read_key(hFITS, TSTRING, "EXTNAME", szExtname, nullptr,
2450 : &status);
2451 77 : status = 0;
2452 77 : int nExtVer = 0;
2453 77 : fits_read_key(hFITS, TINT, "EXTVER", &nExtVer, nullptr, &status);
2454 77 : status = 0;
2455 77 : CPLString osExtname(szExtname);
2456 77 : if (nExtVer > 0)
2457 0 : osExtname += CPLSPrintf(" %d", nExtVer);
2458 :
2459 77 : if (hduType == BINARY_TBL)
2460 : {
2461 14 : hasVector = true;
2462 14 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0)
2463 : {
2464 24 : dataset->m_apoLayers.push_back(std::unique_ptr<FITSLayer>(
2465 12 : new FITSLayer(dataset.get(), iHDU, osExtname.c_str())));
2466 : }
2467 : }
2468 :
2469 77 : if (hduType != IMAGE_HDU)
2470 : {
2471 14 : continue;
2472 : }
2473 :
2474 63 : int bitpix = 0;
2475 63 : int naxis = 0;
2476 63 : const int maxdim = 3;
2477 63 : long naxes[maxdim] = {0, 0, 0};
2478 63 : fits_get_img_param(hFITS, maxdim, &bitpix, &naxis, naxes, &status);
2479 63 : if (status)
2480 : {
2481 0 : continue;
2482 : }
2483 :
2484 63 : if (naxis != 2 && naxis != 3)
2485 : {
2486 17 : if (naxis == 0 && iHDU == 1)
2487 : {
2488 17 : firstHDUIsDummy = true;
2489 : }
2490 17 : continue;
2491 : }
2492 :
2493 46 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0)
2494 : {
2495 41 : const int nIdx = aosSubdatasets.size() / 2 + 1;
2496 : aosSubdatasets.AddNameValue(
2497 : CPLSPrintf("SUBDATASET_%d_NAME", nIdx),
2498 : CPLSPrintf("FITS:\"%s\":%d", poOpenInfo->pszFilename,
2499 41 : iHDU));
2500 : CPLString osDesc(CPLSPrintf(
2501 : "HDU %d (%dx%d, %d band%s)", iHDU,
2502 41 : static_cast<int>(naxes[0]), static_cast<int>(naxes[1]),
2503 51 : naxis == 3 ? static_cast<int>(naxes[2]) : 1,
2504 82 : (naxis == 3 && naxes[2] > 1) ? "s" : ""));
2505 41 : if (!osExtname.empty())
2506 : {
2507 5 : osDesc += ", ";
2508 5 : osDesc += osExtname;
2509 : }
2510 : aosSubdatasets.AddNameValue(
2511 41 : CPLSPrintf("SUBDATASET_%d_DESC", nIdx), osDesc);
2512 : }
2513 :
2514 46 : if (firstValidHDU == 0)
2515 : {
2516 41 : firstValidHDU = iHDU;
2517 : }
2518 : }
2519 54 : if (aosSubdatasets.size() == 2)
2520 : {
2521 35 : aosSubdatasets.Clear();
2522 : }
2523 : }
2524 : else
2525 : {
2526 5 : if (iSelectedHDU != 1)
2527 : {
2528 4 : int hduType = 0;
2529 4 : fits_movabs_hdu(hFITS, 1, &hduType, &status);
2530 4 : if (status == 0)
2531 : {
2532 4 : int bitpix = 0;
2533 4 : int naxis = 0;
2534 4 : const int maxdim = 3;
2535 4 : long naxes[maxdim] = {0, 0, 0};
2536 4 : fits_get_img_param(hFITS, maxdim, &bitpix, &naxis, naxes,
2537 : &status);
2538 4 : if (status == 0 && naxis == 0)
2539 : {
2540 2 : firstHDUIsDummy = true;
2541 : }
2542 : }
2543 4 : status = 0;
2544 : }
2545 5 : firstValidHDU = iSelectedHDU;
2546 : }
2547 :
2548 59 : const bool hasRaster = firstValidHDU > 0;
2549 59 : const bool hasRasterAndIsAllowed =
2550 59 : hasRaster && (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0;
2551 :
2552 59 : if (!hasRasterAndIsAllowed &&
2553 16 : (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
2554 3 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
2555 : {
2556 2 : if (hasVector)
2557 : {
2558 2 : std::string osPath;
2559 1 : osPath.resize(1024);
2560 1 : if (CPLGetExecPath(&osPath[0], static_cast<int>(osPath.size())))
2561 : {
2562 1 : osPath = CPLGetBasename(osPath.c_str());
2563 : }
2564 1 : if (osPath == "gdalinfo")
2565 : {
2566 0 : CPLError(CE_Failure, CPLE_AppDefined,
2567 : "This FITS dataset does not contain any image, but "
2568 : "contains binary table(s) that could be opened "
2569 : "in vector mode with ogrinfo.");
2570 : }
2571 : else
2572 : {
2573 1 : CPLError(CE_Failure, CPLE_AppDefined,
2574 : "This FITS dataset does not contain any image, but "
2575 : "contains binary table(s) that could be opened "
2576 : "in vector mode.");
2577 : }
2578 : }
2579 : else
2580 : {
2581 1 : CPLError(CE_Failure, CPLE_AppDefined,
2582 : "Cannot find HDU of image type with 2 or 3 axes.");
2583 : }
2584 2 : return nullptr;
2585 : }
2586 :
2587 57 : if (dataset->m_apoLayers.empty() &&
2588 60 : (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 &&
2589 3 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0)
2590 : {
2591 3 : if (hasRaster)
2592 : {
2593 4 : std::string osPath;
2594 2 : osPath.resize(1024);
2595 2 : if (CPLGetExecPath(&osPath[0], static_cast<int>(osPath.size())))
2596 : {
2597 2 : osPath = CPLGetBasename(osPath.c_str());
2598 : }
2599 2 : if (osPath == "ogrinfo")
2600 : {
2601 0 : CPLError(CE_Failure, CPLE_AppDefined,
2602 : "This FITS dataset does not contain any binary "
2603 : "table, but contains image(s) that could be opened "
2604 : "in raster mode with gdalinfo.");
2605 : }
2606 : else
2607 : {
2608 2 : CPLError(CE_Failure, CPLE_AppDefined,
2609 : "This FITS dataset does not contain any binary "
2610 : "table, but contains image(s) that could be opened "
2611 : "in raster mode.");
2612 : }
2613 : }
2614 : else
2615 : {
2616 1 : CPLError(CE_Failure, CPLE_AppDefined,
2617 : "Cannot find binary table(s).");
2618 : }
2619 3 : return nullptr;
2620 : }
2621 :
2622 54 : dataset->m_aosSubdatasets = aosSubdatasets;
2623 :
2624 : // Set up the description and initialize the dataset
2625 54 : dataset->SetDescription(poOpenInfo->pszFilename);
2626 54 : if (hasRasterAndIsAllowed)
2627 : {
2628 43 : if (aosSubdatasets.size() > 2)
2629 : {
2630 3 : firstValidHDU = 0;
2631 3 : int hduType = 0;
2632 3 : fits_movabs_hdu(hFITS, 1, &hduType, &status);
2633 : }
2634 : else
2635 : {
2636 80 : if (firstValidHDU != 0 &&
2637 40 : dataset->Init(hFITS, true, firstValidHDU) != CE_None)
2638 : {
2639 1 : return nullptr;
2640 : }
2641 : }
2642 : }
2643 :
2644 : // If the first HDU is a dummy one, load its metadata first, and then
2645 : // add/override it by the one of the image HDU
2646 53 : if (firstHDUIsDummy && firstValidHDU > 1)
2647 : {
2648 4 : int hduType = 0;
2649 4 : status = 0;
2650 4 : fits_movabs_hdu(hFITS, 1, &hduType, &status);
2651 4 : if (status == 0)
2652 : {
2653 4 : dataset->LoadMetadata(dataset.get());
2654 : }
2655 4 : status = 0;
2656 4 : fits_movabs_hdu(hFITS, firstValidHDU, &hduType, &status);
2657 4 : if (status)
2658 : {
2659 0 : return nullptr;
2660 : }
2661 : }
2662 53 : if (hasRasterAndIsAllowed)
2663 : {
2664 42 : dataset->LoadMetadata(dataset.get());
2665 42 : dataset->LoadFITSInfo();
2666 : }
2667 :
2668 : /* -------------------------------------------------------------------- */
2669 : /* Initialize any information. */
2670 : /* -------------------------------------------------------------------- */
2671 53 : dataset->SetDescription(poOpenInfo->pszFilename);
2672 53 : dataset->TryLoadXML();
2673 :
2674 : /* -------------------------------------------------------------------- */
2675 : /* Check for external overviews. */
2676 : /* -------------------------------------------------------------------- */
2677 53 : dataset->oOvManager.Initialize(dataset.get(), poOpenInfo->pszFilename,
2678 : poOpenInfo->GetSiblingFiles());
2679 :
2680 53 : return dataset.release();
2681 : }
2682 :
2683 : /************************************************************************/
2684 : /* Create() */
2685 : /* */
2686 : /* Create a new FITS file. */
2687 : /************************************************************************/
2688 :
2689 67 : GDALDataset *FITSDataset::Create(const char *pszFilename, int nXSize,
2690 : int nYSize, int nBandsIn, GDALDataType eType,
2691 : CPL_UNUSED char **papszParamList)
2692 : {
2693 67 : int status = 0;
2694 :
2695 67 : if (nXSize == 0 && nYSize == 0 && nBandsIn == 0 && eType == GDT_Unknown)
2696 : {
2697 : // Create the file - to force creation, we prepend the name with '!'
2698 8 : CPLString extFilename("!");
2699 4 : extFilename += pszFilename;
2700 4 : fitsfile *hFITS = nullptr;
2701 4 : fits_create_file(&hFITS, extFilename, &status);
2702 4 : if (status)
2703 : {
2704 0 : CPLError(CE_Failure, CPLE_AppDefined,
2705 : "Couldn't create FITS file %s (%d).\n", pszFilename,
2706 : status);
2707 0 : return nullptr;
2708 : }
2709 :
2710 : // Likely vector creation mode
2711 4 : FITSDataset *dataset = new FITSDataset();
2712 4 : dataset->m_hFITS = hFITS;
2713 4 : dataset->eAccess = GA_Update;
2714 4 : dataset->SetDescription(pszFilename);
2715 4 : return dataset;
2716 : }
2717 :
2718 : // No creation options are defined. The BSCALE/BZERO options were
2719 : // removed on 2002-07-02 by Simon Perkins because they introduced
2720 : // excessive complications and didn't really fit into the GDAL
2721 : // paradigm.
2722 : // 2018 - BZERO BSCALE keywords are now set using SetScale() and
2723 : // SetOffset() functions
2724 :
2725 63 : if (nXSize < 1 || nYSize < 1 || nBandsIn < 1)
2726 : {
2727 2 : CPLError(CE_Failure, CPLE_AppDefined,
2728 : "Attempt to create %dx%dx%d raster FITS file, but width, "
2729 : "height and bands"
2730 : " must be positive.",
2731 : nXSize, nYSize, nBandsIn);
2732 :
2733 2 : return nullptr;
2734 : }
2735 :
2736 : // Determine FITS type of image
2737 : int bitpix;
2738 61 : if (eType == GDT_Byte)
2739 : {
2740 17 : bitpix = BYTE_IMG;
2741 : }
2742 44 : else if (eType == GDT_UInt16)
2743 : {
2744 4 : bitpix = USHORT_IMG;
2745 : }
2746 40 : else if (eType == GDT_Int16)
2747 : {
2748 6 : bitpix = SHORT_IMG;
2749 : }
2750 34 : else if (eType == GDT_UInt32)
2751 : {
2752 4 : bitpix = ULONG_IMG;
2753 : }
2754 30 : else if (eType == GDT_Int32)
2755 : {
2756 4 : bitpix = LONG_IMG;
2757 : }
2758 26 : else if (eType == GDT_Float32)
2759 4 : bitpix = FLOAT_IMG;
2760 22 : else if (eType == GDT_Float64)
2761 4 : bitpix = DOUBLE_IMG;
2762 : else
2763 : {
2764 18 : CPLError(CE_Failure, CPLE_AppDefined,
2765 : "GDALDataType (%d) unsupported for FITS", eType);
2766 18 : return nullptr;
2767 : }
2768 :
2769 : // Create the file - to force creation, we prepend the name with '!'
2770 86 : CPLString extFilename("!");
2771 43 : extFilename += pszFilename;
2772 43 : fitsfile *hFITS = nullptr;
2773 43 : fits_create_file(&hFITS, extFilename, &status);
2774 43 : if (status)
2775 : {
2776 3 : CPLError(CE_Failure, CPLE_AppDefined,
2777 : "Couldn't create FITS file %s (%d).\n", pszFilename, status);
2778 3 : return nullptr;
2779 : }
2780 :
2781 : // Now create an image of appropriate size and type
2782 40 : long naxes[3] = {nXSize, nYSize, nBandsIn};
2783 40 : int naxis = (nBandsIn == 1) ? 2 : 3;
2784 40 : fits_create_img(hFITS, bitpix, naxis, naxes, &status);
2785 :
2786 : // Check the status
2787 40 : if (status)
2788 : {
2789 0 : CPLError(CE_Failure, CPLE_AppDefined,
2790 : "Couldn't create image within FITS file %s (%d).", pszFilename,
2791 : status);
2792 0 : fits_close_file(hFITS, &status);
2793 0 : return nullptr;
2794 : }
2795 :
2796 40 : FITSDataset *dataset = new FITSDataset();
2797 40 : dataset->nRasterXSize = nXSize;
2798 40 : dataset->nRasterYSize = nYSize;
2799 40 : dataset->eAccess = GA_Update;
2800 40 : dataset->SetDescription(pszFilename);
2801 :
2802 : // Init recalculates a lot of stuff we already know, but...
2803 40 : if (dataset->Init(hFITS, false, 1) != CE_None)
2804 : {
2805 0 : delete dataset;
2806 0 : return nullptr;
2807 : }
2808 : else
2809 : {
2810 40 : return dataset;
2811 : }
2812 : }
2813 :
2814 : /************************************************************************/
2815 : /* Delete() */
2816 : /************************************************************************/
2817 :
2818 13 : CPLErr FITSDataset::Delete(const char *pszFilename)
2819 : {
2820 13 : return VSIUnlink(pszFilename) == 0 ? CE_None : CE_Failure;
2821 : }
2822 :
2823 : /************************************************************************/
2824 : /* WriteFITSInfo() */
2825 : /************************************************************************/
2826 :
2827 40 : void FITSDataset::WriteFITSInfo()
2828 :
2829 : {
2830 40 : int status = 0;
2831 :
2832 40 : const double PI = std::atan(1.0) * 4;
2833 40 : const double DEG2RAD = PI / 180.;
2834 :
2835 40 : double falseEast = 0;
2836 40 : double falseNorth = 0;
2837 :
2838 : double cfactor, mres, mapres, UpperLeftCornerX, UpperLeftCornerY;
2839 : double crpix1, crpix2;
2840 :
2841 : /* -------------------------------------------------------------------- */
2842 : /* Write out projection definition. */
2843 : /* -------------------------------------------------------------------- */
2844 40 : const bool bHasProjection = !m_oSRS.IsEmpty();
2845 40 : if (bHasProjection)
2846 : {
2847 :
2848 : // Set according to coordinate system (thanks to Trent Hare - USGS)
2849 :
2850 40 : std::string object, ctype1, ctype2;
2851 :
2852 40 : const char *target = m_oSRS.GetAttrValue("DATUM", 0);
2853 40 : if (target)
2854 : {
2855 40 : if (strstr(target, "Moon"))
2856 : {
2857 0 : object.assign("Moon");
2858 0 : ctype1.assign("SE");
2859 0 : ctype2.assign("SE");
2860 : }
2861 40 : else if (strstr(target, "Mercury"))
2862 : {
2863 0 : object.assign("Mercury");
2864 0 : ctype1.assign("ME");
2865 0 : ctype2.assign("ME");
2866 : }
2867 40 : else if (strstr(target, "Venus"))
2868 : {
2869 0 : object.assign("Venus");
2870 0 : ctype1.assign("VE");
2871 0 : ctype2.assign("VE");
2872 : }
2873 40 : else if (strstr(target, "Mars"))
2874 : {
2875 0 : object.assign("Mars");
2876 0 : ctype1.assign("MA");
2877 0 : ctype2.assign("MA");
2878 : }
2879 40 : else if (strstr(target, "Jupiter"))
2880 : {
2881 0 : object.assign("Jupiter");
2882 0 : ctype1.assign("JU");
2883 0 : ctype2.assign("JU");
2884 : }
2885 40 : else if (strstr(target, "Saturn"))
2886 : {
2887 0 : object.assign("Saturn");
2888 0 : ctype1.assign("SA");
2889 0 : ctype2.assign("SA");
2890 : }
2891 40 : else if (strstr(target, "Uranus"))
2892 : {
2893 0 : object.assign("Uranus");
2894 0 : ctype1.assign("UR");
2895 0 : ctype2.assign("UR");
2896 : }
2897 40 : else if (strstr(target, "Neptune"))
2898 : {
2899 0 : object.assign("Neptune");
2900 0 : ctype1.assign("NE");
2901 0 : ctype2.assign("NE");
2902 : }
2903 : else
2904 : {
2905 40 : object.assign("Earth");
2906 40 : ctype1.assign("EA");
2907 40 : ctype2.assign("EA");
2908 : }
2909 :
2910 40 : fits_update_key(
2911 : m_hFITS, TSTRING, "OBJECT",
2912 40 : const_cast<void *>(static_cast<const void *>(object.c_str())),
2913 : nullptr, &status);
2914 : }
2915 :
2916 40 : double aradius = m_oSRS.GetSemiMajor();
2917 40 : double bradius = aradius;
2918 40 : double cradius = m_oSRS.GetSemiMinor();
2919 :
2920 40 : cfactor = aradius * DEG2RAD;
2921 :
2922 40 : fits_update_key(m_hFITS, TDOUBLE, "A_RADIUS", &aradius, nullptr,
2923 : &status);
2924 40 : if (status)
2925 : {
2926 : // Throw a warning with CFITSIO error status, then ignore status
2927 0 : CPLError(CE_Warning, CPLE_AppDefined,
2928 : "Couldn't update key A_RADIUS in FITS file %s (%d).",
2929 0 : GetDescription(), status);
2930 0 : status = 0;
2931 0 : return;
2932 : }
2933 40 : fits_update_key(m_hFITS, TDOUBLE, "B_RADIUS", &bradius, nullptr,
2934 : &status);
2935 40 : if (status)
2936 : {
2937 : // Throw a warning with CFITSIO error status, then ignore status
2938 0 : CPLError(CE_Warning, CPLE_AppDefined,
2939 : "Couldn't update key B_RADIUS in FITS file %s (%d).",
2940 0 : GetDescription(), status);
2941 0 : status = 0;
2942 0 : return;
2943 : }
2944 40 : fits_update_key(m_hFITS, TDOUBLE, "C_RADIUS", &cradius, nullptr,
2945 : &status);
2946 40 : if (status)
2947 : {
2948 : // Throw a warning with CFITSIO error status, then ignore status
2949 0 : CPLError(CE_Warning, CPLE_AppDefined,
2950 : "Couldn't update key C_RADIUS in FITS file %s (%d).",
2951 0 : GetDescription(), status);
2952 0 : status = 0;
2953 0 : return;
2954 : }
2955 :
2956 40 : const char *unit = m_oSRS.GetAttrValue("UNIT", 0);
2957 :
2958 40 : ctype1.append("LN-");
2959 40 : ctype2.append("LT-");
2960 :
2961 : // strcat(ctype1a, "PX-");
2962 : // strcat(ctype2a, "PY-");
2963 :
2964 40 : std::string fitsproj;
2965 40 : const char *projection = m_oSRS.GetAttrValue("PROJECTION", 0);
2966 40 : double centlon = 0, centlat = 0;
2967 :
2968 40 : if (projection)
2969 : {
2970 12 : if (strstr(projection, "Sinusoidal"))
2971 : {
2972 0 : fitsproj.assign("SFL");
2973 0 : centlon = m_oSRS.GetProjParm("central_meridian", 0, nullptr);
2974 : }
2975 12 : else if (strstr(projection, "Equirectangular"))
2976 : {
2977 0 : fitsproj.assign("CAR");
2978 0 : centlat = m_oSRS.GetProjParm("standard_parallel_1", 0, nullptr);
2979 0 : centlon = m_oSRS.GetProjParm("central_meridian", 0, nullptr);
2980 : }
2981 12 : else if (strstr(projection, "Orthographic"))
2982 : {
2983 0 : fitsproj.assign("SIN");
2984 0 : centlat = m_oSRS.GetProjParm("standard_parallel_1", 0, nullptr);
2985 0 : centlon = m_oSRS.GetProjParm("central_meridian", 0, nullptr);
2986 : }
2987 12 : else if (strstr(projection, "Mercator_1SP") ||
2988 12 : strstr(projection, "Mercator"))
2989 : {
2990 12 : fitsproj.assign("MER");
2991 12 : centlat = m_oSRS.GetProjParm("standard_parallel_1", 0, nullptr);
2992 12 : centlon = m_oSRS.GetProjParm("central_meridian", 0, nullptr);
2993 : }
2994 0 : else if (strstr(projection, "Polar_Stereographic") ||
2995 0 : strstr(projection, "Stereographic_South_Pole") ||
2996 0 : strstr(projection, "Stereographic_North_Pole"))
2997 : {
2998 0 : fitsproj.assign("STG");
2999 0 : centlat = m_oSRS.GetProjParm("latitude_of_origin", 0, nullptr);
3000 0 : centlon = m_oSRS.GetProjParm("central_meridian", 0, nullptr);
3001 : }
3002 :
3003 : /*
3004 : #Transverse Mercator is supported in FITS via
3005 : specific MER parameters. # need some more testing... #if
3006 : EQUAL(mapProjection,"Transverse_Mercator"): # mapProjection =
3007 : "MER" # centLat = hSRS.GetProjParm('standard_parallel_1') #
3008 : centLon = hSRS.GetProjParm('central_meridian') # TMscale =
3009 : hSRS.GetProjParm('scale_factor') # #Need to research when TM
3010 : actually applies false values # #but planetary is almost
3011 : always 0.0 # falseEast = hSRS.GetProjParm('false_easting') #
3012 : falseNorth = hSRS.GetProjParm('false_northing')
3013 : */
3014 :
3015 12 : ctype1.append(fitsproj);
3016 12 : ctype2.append(fitsproj);
3017 :
3018 12 : fits_update_key(
3019 : m_hFITS, TSTRING, "CTYPE1",
3020 12 : const_cast<void *>(static_cast<const void *>(ctype1.c_str())),
3021 : nullptr, &status);
3022 12 : if (status)
3023 : {
3024 : // Throw a warning with CFITSIO error status, then ignore status
3025 0 : CPLError(CE_Warning, CPLE_AppDefined,
3026 : "Couldn't update key CTYPE1 in FITS file %s (%d).",
3027 0 : GetDescription(), status);
3028 0 : status = 0;
3029 0 : return;
3030 : }
3031 :
3032 12 : fits_update_key(
3033 : m_hFITS, TSTRING, "CTYPE2",
3034 12 : const_cast<void *>(static_cast<const void *>(ctype2.c_str())),
3035 : nullptr, &status);
3036 12 : if (status)
3037 : {
3038 : // Throw a warning with CFITSIO error status, then ignore status
3039 0 : CPLError(CE_Warning, CPLE_AppDefined,
3040 : "Couldn't update key CTYPE2 in FITS file %s (%d).",
3041 0 : GetDescription(), status);
3042 0 : status = 0;
3043 0 : return;
3044 : }
3045 : }
3046 :
3047 40 : UpperLeftCornerX = m_adfGeoTransform[0] - falseEast;
3048 40 : UpperLeftCornerY = m_adfGeoTransform[3] - falseNorth;
3049 :
3050 40 : if (centlon > 180.)
3051 : {
3052 0 : centlon = centlon - 180.;
3053 : }
3054 40 : if (strstr(unit, "metre"))
3055 : {
3056 : // convert degrees/pixel to m/pixel
3057 12 : mapres = 1. / m_adfGeoTransform[1]; // mapres is pixel/meters
3058 12 : mres = m_adfGeoTransform[1] / cfactor; // mres is deg/pixel
3059 12 : crpix1 = -(UpperLeftCornerX * mapres) + centlon / mres + 0.5;
3060 : // assuming that center latitude is also the origin of the
3061 : // coordinate system: this is not always true. More generic
3062 : // implementation coming soon
3063 12 : crpix2 = (UpperLeftCornerY * mapres) + 0.5; // - (centlat / mres);
3064 : }
3065 28 : else if (strstr(unit, "degree"))
3066 : {
3067 : // convert m/pixel to pixel/degree
3068 28 : mapres =
3069 28 : 1. / m_adfGeoTransform[1] / cfactor; // mapres is pixel/deg
3070 28 : mres = m_adfGeoTransform[1]; // mres is meters/pixel
3071 28 : crpix1 = -(UpperLeftCornerX * mres) + centlon / mapres + 0.5;
3072 : // assuming that center latitude is also the origin of the
3073 : // coordinate system: this is not always true. More generic
3074 : // implementation coming soon
3075 28 : crpix2 = (UpperLeftCornerY * mres) + 0.5; // - (centlat / mapres);
3076 : }
3077 :
3078 : /// Write WCS CRPIXia CRVALia CTYPEia here
3079 :
3080 40 : fits_update_key(m_hFITS, TDOUBLE, "CRVAL1", ¢lon, nullptr, &status);
3081 40 : if (status)
3082 : {
3083 : // Throw a warning with CFITSIO error status, then ignore status
3084 0 : CPLError(CE_Warning, CPLE_AppDefined,
3085 : "Couldn't update key CRVAL1 in FITS file %s (%d).",
3086 0 : GetDescription(), status);
3087 0 : status = 0;
3088 0 : return;
3089 : }
3090 40 : fits_update_key(m_hFITS, TDOUBLE, "CRVAL2", ¢lat, nullptr, &status);
3091 40 : if (status)
3092 : {
3093 : // Throw a warning with CFITSIO error status, then ignore status
3094 0 : CPLError(CE_Warning, CPLE_AppDefined,
3095 : "Couldn't update key CRVAL2 in FITS file %s (%d).",
3096 0 : GetDescription(), status);
3097 0 : status = 0;
3098 0 : return;
3099 : }
3100 40 : fits_update_key(m_hFITS, TDOUBLE, "CRPIX1", &crpix1, nullptr, &status);
3101 40 : if (status)
3102 : {
3103 : // Throw a warning with CFITSIO error status, then ignore status
3104 0 : CPLError(CE_Warning, CPLE_AppDefined,
3105 : "Couldn't update key CRPIX1 in FITS file %s (%d).",
3106 0 : GetDescription(), status);
3107 0 : status = 0;
3108 0 : return;
3109 : }
3110 40 : fits_update_key(m_hFITS, TDOUBLE, "CRPIX2", &crpix2, nullptr, &status);
3111 40 : if (status)
3112 : {
3113 : // Throw a warning with CFITSIO error status, then ignore status
3114 0 : CPLError(CE_Warning, CPLE_AppDefined,
3115 : "Couldn't update key CRPIX2 in FITS file %s (%d).",
3116 0 : GetDescription(), status);
3117 0 : status = 0;
3118 0 : return;
3119 : }
3120 :
3121 : /* --------------------------------------------------------------------
3122 : */
3123 : /* Write geotransform if valid. */
3124 : /* --------------------------------------------------------------------
3125 : */
3126 40 : if (m_bGeoTransformValid)
3127 : {
3128 :
3129 : /* --------------------------------------------------------------------
3130 : */
3131 : /* Write the transform. */
3132 : /* --------------------------------------------------------------------
3133 : */
3134 :
3135 : /// Write WCS CDELTia and PCi_ja here
3136 :
3137 : double cd[4];
3138 40 : cd[0] = m_adfGeoTransform[1] / cfactor;
3139 40 : cd[1] = m_adfGeoTransform[2] / cfactor;
3140 40 : cd[2] = m_adfGeoTransform[4] / cfactor;
3141 40 : cd[3] = m_adfGeoTransform[5] / cfactor;
3142 :
3143 : double pc[4];
3144 40 : pc[0] = 1.;
3145 40 : pc[1] = cd[1] / cd[0];
3146 40 : pc[2] = cd[2] / cd[3];
3147 40 : pc[3] = -1.;
3148 :
3149 40 : fits_update_key(m_hFITS, TDOUBLE, "CDELT1", &cd[0], nullptr,
3150 : &status);
3151 40 : if (status)
3152 : {
3153 : // Throw a warning with CFITSIO error status, then ignore status
3154 0 : CPLError(CE_Warning, CPLE_AppDefined,
3155 : "Couldn't update key CDELT1 in FITS file %s (%d).",
3156 0 : GetDescription(), status);
3157 0 : status = 0;
3158 0 : return;
3159 : }
3160 :
3161 40 : fits_update_key(m_hFITS, TDOUBLE, "CDELT2", &cd[3], nullptr,
3162 : &status);
3163 40 : if (status)
3164 : {
3165 : // Throw a warning with CFITSIO error status, then ignore status
3166 0 : CPLError(CE_Warning, CPLE_AppDefined,
3167 : "Couldn't update key CDELT2 in FITS file %s (%d).",
3168 0 : GetDescription(), status);
3169 0 : status = 0;
3170 0 : return;
3171 : }
3172 :
3173 40 : fits_update_key(m_hFITS, TDOUBLE, "PC1_1", &pc[0], nullptr,
3174 : &status);
3175 40 : if (status)
3176 : {
3177 : // Throw a warning with CFITSIO error status, then ignore status
3178 0 : CPLError(CE_Warning, CPLE_AppDefined,
3179 : "Couldn't update key PC1_1 in FITS file %s (%d).",
3180 0 : GetDescription(), status);
3181 0 : status = 0;
3182 0 : return;
3183 : }
3184 :
3185 40 : fits_update_key(m_hFITS, TDOUBLE, "PC1_2", &pc[1], nullptr,
3186 : &status);
3187 40 : if (status)
3188 : {
3189 : // Throw a warning with CFITSIO error status, then ignore status
3190 0 : CPLError(CE_Warning, CPLE_AppDefined,
3191 : "Couldn't update key PC1_2 in FITS file %s (%d).",
3192 0 : GetDescription(), status);
3193 0 : status = 0;
3194 0 : return;
3195 : }
3196 :
3197 40 : fits_update_key(m_hFITS, TDOUBLE, "PC2_1", &pc[2], nullptr,
3198 : &status);
3199 40 : if (status)
3200 : {
3201 : // Throw a warning with CFITSIO error status, then ignore status
3202 0 : CPLError(CE_Warning, CPLE_AppDefined,
3203 : "Couldn't update key PC2_1 in FITS file %s (%d).",
3204 0 : GetDescription(), status);
3205 0 : status = 0;
3206 0 : return;
3207 : }
3208 :
3209 40 : fits_update_key(m_hFITS, TDOUBLE, "PC2_2", &pc[3], nullptr,
3210 : &status);
3211 40 : if (status)
3212 : {
3213 : // Throw a warning with CFITSIO error status, then ignore status
3214 0 : CPLError(CE_Warning, CPLE_AppDefined,
3215 : "Couldn't update key PC2_2 in FITS file %s (%d).",
3216 0 : GetDescription(), status);
3217 0 : status = 0;
3218 0 : return;
3219 : }
3220 : }
3221 : }
3222 : }
3223 :
3224 : /************************************************************************/
3225 : /* GetSpatialRef() */
3226 : /************************************************************************/
3227 :
3228 4 : const OGRSpatialReference *FITSDataset::GetSpatialRef() const
3229 :
3230 : {
3231 4 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3232 : }
3233 :
3234 : /************************************************************************/
3235 : /* SetSpatialRef() */
3236 : /************************************************************************/
3237 :
3238 40 : CPLErr FITSDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
3239 :
3240 : {
3241 40 : if (poSRS == nullptr || poSRS->IsEmpty())
3242 : {
3243 0 : m_oSRS.Clear();
3244 : }
3245 : else
3246 : {
3247 40 : m_oSRS = *poSRS;
3248 40 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3249 : }
3250 :
3251 40 : m_bFITSInfoChanged = true;
3252 :
3253 40 : return CE_None;
3254 : }
3255 :
3256 : /************************************************************************/
3257 : /* GetGeoTransform() */
3258 : /************************************************************************/
3259 :
3260 19 : CPLErr FITSDataset::GetGeoTransform(double *padfTransform)
3261 :
3262 : {
3263 19 : memcpy(padfTransform, m_adfGeoTransform, sizeof(double) * 6);
3264 :
3265 19 : if (!m_bGeoTransformValid)
3266 0 : return CE_Failure;
3267 :
3268 19 : return CE_None;
3269 : }
3270 :
3271 : /************************************************************************/
3272 : /* SetGeoTransform() */
3273 : /************************************************************************/
3274 :
3275 40 : CPLErr FITSDataset::SetGeoTransform(double *padfTransform)
3276 :
3277 : {
3278 40 : memcpy(m_adfGeoTransform, padfTransform, sizeof(double) * 6);
3279 40 : m_bGeoTransformValid = true;
3280 :
3281 40 : return CE_None;
3282 : }
3283 :
3284 : /************************************************************************/
3285 : /* GetOffset() */
3286 : /************************************************************************/
3287 :
3288 44 : double FITSRasterBand::GetOffset(int *pbSuccess)
3289 :
3290 : {
3291 44 : if (pbSuccess)
3292 43 : *pbSuccess = m_bHaveOffsetScale;
3293 44 : return m_dfOffset;
3294 : }
3295 :
3296 : /************************************************************************/
3297 : /* SetOffset() */
3298 : /************************************************************************/
3299 :
3300 1 : CPLErr FITSRasterBand::SetOffset(double dfNewValue)
3301 :
3302 : {
3303 1 : if (!m_bHaveOffsetScale || dfNewValue != m_dfOffset)
3304 1 : m_poFDS->m_bMetadataChanged = true;
3305 :
3306 1 : m_bHaveOffsetScale = true;
3307 1 : m_dfOffset = dfNewValue;
3308 1 : return CE_None;
3309 : }
3310 :
3311 : /************************************************************************/
3312 : /* GetScale() */
3313 : /************************************************************************/
3314 :
3315 44 : double FITSRasterBand::GetScale(int *pbSuccess)
3316 :
3317 : {
3318 44 : if (pbSuccess)
3319 43 : *pbSuccess = m_bHaveOffsetScale;
3320 44 : return m_dfScale;
3321 : }
3322 :
3323 : /************************************************************************/
3324 : /* SetScale() */
3325 : /************************************************************************/
3326 :
3327 1 : CPLErr FITSRasterBand::SetScale(double dfNewValue)
3328 :
3329 : {
3330 1 : if (!m_bHaveOffsetScale || dfNewValue != m_dfScale)
3331 1 : m_poFDS->m_bMetadataChanged = true;
3332 :
3333 1 : m_bHaveOffsetScale = true;
3334 1 : m_dfScale = dfNewValue;
3335 1 : return CE_None;
3336 : }
3337 :
3338 : /************************************************************************/
3339 : /* GetNoDataValue() */
3340 : /************************************************************************/
3341 :
3342 2 : double FITSRasterBand::GetNoDataValue(int *pbSuccess)
3343 :
3344 : {
3345 2 : if (m_bNoDataSet)
3346 : {
3347 0 : if (pbSuccess)
3348 0 : *pbSuccess = TRUE;
3349 :
3350 0 : return m_dfNoDataValue;
3351 : }
3352 :
3353 2 : if (m_poFDS->m_bNoDataSet)
3354 : {
3355 2 : if (pbSuccess)
3356 2 : *pbSuccess = TRUE;
3357 :
3358 2 : return m_poFDS->m_dfNoDataValue;
3359 : }
3360 :
3361 0 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
3362 : }
3363 :
3364 : /************************************************************************/
3365 : /* SetNoDataValue() */
3366 : /************************************************************************/
3367 :
3368 1 : CPLErr FITSRasterBand::SetNoDataValue(double dfNoData)
3369 :
3370 : {
3371 1 : if (m_poFDS->m_bNoDataSet && m_poFDS->m_dfNoDataValue == dfNoData)
3372 : {
3373 0 : m_bNoDataSet = true;
3374 0 : m_dfNoDataValue = dfNoData;
3375 0 : return CE_None;
3376 : }
3377 :
3378 1 : m_poFDS->m_bNoDataSet = true;
3379 1 : m_poFDS->m_dfNoDataValue = dfNoData;
3380 :
3381 1 : m_poFDS->m_bNoDataChanged = true;
3382 :
3383 1 : m_bNoDataSet = true;
3384 1 : m_dfNoDataValue = dfNoData;
3385 1 : return CE_None;
3386 : }
3387 :
3388 : /************************************************************************/
3389 : /* DeleteNoDataValue() */
3390 : /************************************************************************/
3391 :
3392 0 : CPLErr FITSRasterBand::DeleteNoDataValue()
3393 :
3394 : {
3395 0 : if (!m_poFDS->m_bNoDataSet)
3396 0 : return CE_None;
3397 :
3398 0 : m_poFDS->m_bNoDataSet = false;
3399 0 : m_poFDS->m_dfNoDataValue = -9999.0;
3400 :
3401 0 : m_poFDS->m_bNoDataChanged = true;
3402 :
3403 0 : m_bNoDataSet = false;
3404 0 : m_dfNoDataValue = -9999.0;
3405 0 : return CE_None;
3406 : }
3407 :
3408 : /************************************************************************/
3409 : /* LoadGeoreferencing() */
3410 : /************************************************************************/
3411 :
3412 42 : void FITSDataset::LoadGeoreferencing()
3413 : {
3414 42 : int status = 0;
3415 : double crpix1, crpix2, crval1, crval2, cdelt1, cdelt2, pc[4], cd[4];
3416 42 : double aRadius, cRadius, invFlattening = 0.0;
3417 42 : double falseEast = 0.0, falseNorth = 0.0, scale = 1.0;
3418 : char target[81], ctype[81];
3419 42 : std::string GeogName, DatumName, projName;
3420 :
3421 42 : const double PI = std::atan(1.0) * 4;
3422 42 : const double DEG2RAD = PI / 180.;
3423 :
3424 : /* -------------------------------------------------------------------- */
3425 : /* Get the transform from the FITS file. */
3426 : /* -------------------------------------------------------------------- */
3427 :
3428 42 : fits_read_key(m_hFITS, TSTRING, "OBJECT", target, nullptr, &status);
3429 42 : if (status)
3430 : {
3431 9 : strncpy(target, "Undefined", 10);
3432 9 : CPLDebug("FITS", "OBJECT keyword is missing");
3433 9 : status = 0;
3434 : }
3435 :
3436 42 : GeogName.assign("GCS_");
3437 42 : GeogName.append(target);
3438 42 : DatumName.assign("D_");
3439 42 : DatumName.append(target);
3440 :
3441 42 : fits_read_key(m_hFITS, TDOUBLE, "A_RADIUS", &aRadius, nullptr, &status);
3442 42 : if (status)
3443 : {
3444 9 : CPLDebug("FITS", "No Radii keyword available, metadata will not "
3445 : "contain DATUM information.");
3446 9 : return;
3447 : }
3448 : else
3449 : {
3450 33 : fits_read_key(m_hFITS, TDOUBLE, "C_RADIUS", &cRadius, nullptr, &status);
3451 33 : if (status)
3452 : {
3453 0 : CPLError(CE_Warning, CPLE_AppDefined,
3454 : "No polar radius keyword available, setting C_RADIUS = "
3455 : "A_RADIUS");
3456 0 : cRadius = aRadius;
3457 0 : status = 0;
3458 : }
3459 33 : if (aRadius != cRadius)
3460 : {
3461 33 : invFlattening = aRadius / (aRadius - cRadius);
3462 : }
3463 : }
3464 :
3465 : /* Waiting for linear keywords standardization only deg ctype are used */
3466 : /* Check if WCS are there */
3467 33 : fits_read_key(m_hFITS, TSTRING, "CTYPE1", ctype, nullptr, &status);
3468 33 : if (!status)
3469 : {
3470 : /* Check if angular WCS are there */
3471 16 : if (strstr(ctype, "LN"))
3472 : {
3473 : /* Reading reference points */
3474 16 : fits_read_key(m_hFITS, TDOUBLE, "CRPIX1", &crpix1, nullptr,
3475 : &status);
3476 16 : fits_read_key(m_hFITS, TDOUBLE, "CRPIX2", &crpix2, nullptr,
3477 : &status);
3478 16 : fits_read_key(m_hFITS, TDOUBLE, "CRVAL1", &crval1, nullptr,
3479 : &status);
3480 16 : fits_read_key(m_hFITS, TDOUBLE, "CRVAL2", &crval2, nullptr,
3481 : &status);
3482 16 : if (status)
3483 : {
3484 0 : CPLError(CE_Failure, CPLE_AppDefined,
3485 : "No CRPIX / CRVAL keyword available, the raster "
3486 : "cannot be georeferenced.");
3487 0 : status = 0;
3488 : }
3489 : else
3490 : {
3491 : /* Check for CDELT and PC matrix representation */
3492 16 : fits_read_key(m_hFITS, TDOUBLE, "CDELT1", &cdelt1, nullptr,
3493 : &status);
3494 16 : if (!status)
3495 : {
3496 16 : fits_read_key(m_hFITS, TDOUBLE, "CDELT2", &cdelt2, nullptr,
3497 : &status);
3498 16 : fits_read_key(m_hFITS, TDOUBLE, "PC1_1", &pc[0], nullptr,
3499 : &status);
3500 16 : fits_read_key(m_hFITS, TDOUBLE, "PC1_2", &pc[1], nullptr,
3501 : &status);
3502 16 : fits_read_key(m_hFITS, TDOUBLE, "PC2_1", &pc[2], nullptr,
3503 : &status);
3504 16 : fits_read_key(m_hFITS, TDOUBLE, "PC2_2", &pc[3], nullptr,
3505 : &status);
3506 16 : cd[0] = cdelt1 * pc[0];
3507 16 : cd[1] = cdelt1 * pc[1];
3508 16 : cd[2] = cdelt2 * pc[2];
3509 16 : cd[3] = cdelt2 * pc[3];
3510 16 : status = 0;
3511 : }
3512 : else
3513 : {
3514 : /* Look for CD matrix representation */
3515 0 : fits_read_key(m_hFITS, TDOUBLE, "CD1_1", &cd[0], nullptr,
3516 : &status);
3517 0 : fits_read_key(m_hFITS, TDOUBLE, "CD1_2", &cd[1], nullptr,
3518 : &status);
3519 0 : fits_read_key(m_hFITS, TDOUBLE, "CD2_1", &cd[2], nullptr,
3520 : &status);
3521 0 : fits_read_key(m_hFITS, TDOUBLE, "CD2_2", &cd[3], nullptr,
3522 : &status);
3523 : }
3524 :
3525 16 : double radfac = DEG2RAD * aRadius;
3526 :
3527 16 : m_adfGeoTransform[1] = cd[0] * radfac;
3528 16 : m_adfGeoTransform[2] = cd[1] * radfac;
3529 16 : m_adfGeoTransform[4] = cd[2] * radfac;
3530 16 : m_adfGeoTransform[5] = -cd[3] * radfac;
3531 16 : if (crval1 > 180.)
3532 : {
3533 0 : crval1 = crval1 - 180.;
3534 : }
3535 :
3536 : /* NOTA BENE: FITS standard define pixel integers at the center
3537 : of the pixel, 0.5 must be subtract to have UpperLeft corner
3538 : */
3539 16 : m_adfGeoTransform[0] =
3540 16 : crval1 * radfac - m_adfGeoTransform[1] * (crpix1 - 0.5);
3541 : // assuming that center latitude is also the origin of the
3542 : // coordinate system: this is not always true. More generic
3543 : // implementation coming soon
3544 16 : m_adfGeoTransform[3] = -m_adfGeoTransform[5] * (crpix2 - 0.5);
3545 : //+ crval2 * radfac;
3546 16 : m_bGeoTransformValid = true;
3547 : }
3548 :
3549 16 : char *pstr = strrchr(ctype, '-');
3550 16 : if (pstr)
3551 : {
3552 16 : pstr += 1;
3553 :
3554 : /* Defining projection type
3555 : Following http://www.gdal.org/ogr__srs__api_8h.html (GDAL)
3556 : and
3557 : http://www.aanda.org/component/article?access=bibcode&bibcode=&bibcode=2002A%2526A...395.1077CFUL
3558 : (FITS)
3559 : */
3560 :
3561 : /* Sinusoidal / SFL projection */
3562 16 : if (strcmp(pstr, "SFL") == 0)
3563 : {
3564 0 : projName.assign("Sinusoidal_");
3565 0 : m_oSRS.SetSinusoidal(crval1, falseEast, falseNorth);
3566 :
3567 : /* Mercator, Oblique (Hotine) Mercator, Transverse Mercator
3568 : */
3569 : /* Mercator / MER projection */
3570 : }
3571 16 : else if (strcmp(pstr, "MER") == 0)
3572 : {
3573 16 : projName.assign("Mercator_");
3574 16 : m_oSRS.SetMercator(crval2, crval1, scale, falseEast,
3575 : falseNorth);
3576 :
3577 : /* Equirectangular / CAR projection */
3578 : }
3579 0 : else if (strcmp(pstr, "CAR") == 0)
3580 : {
3581 0 : projName.assign("Equirectangular_");
3582 : /*
3583 : The standard_parallel_1 defines where the local radius is
3584 : calculated not the center of Y Cartesian system (which is
3585 : latitude_of_origin) But FITS WCS only supports projections
3586 : on the sphere we assume here that the local radius is the
3587 : one computed at the projection center
3588 : */
3589 0 : m_oSRS.SetEquirectangular2(crval2, crval1, crval2,
3590 : falseEast, falseNorth);
3591 : /* Lambert Azimuthal Equal Area / ZEA projection */
3592 : }
3593 0 : else if (strcmp(pstr, "ZEA") == 0)
3594 : {
3595 0 : projName.assign("Lambert_Azimuthal_Equal_Area_");
3596 0 : m_oSRS.SetLAEA(crval2, crval1, falseEast, falseNorth);
3597 :
3598 : /* Lambert Conformal Conic 1SP / COO projection */
3599 : }
3600 0 : else if (strcmp(pstr, "COO") == 0)
3601 : {
3602 0 : projName.assign("Lambert_Conformal_Conic_1SP_");
3603 0 : m_oSRS.SetLCC1SP(crval2, crval1, scale, falseEast,
3604 : falseNorth);
3605 :
3606 : /* Orthographic / SIN projection */
3607 : }
3608 0 : else if (strcmp(pstr, "SIN") == 0)
3609 : {
3610 0 : projName.assign("Orthographic_");
3611 0 : m_oSRS.SetOrthographic(crval2, crval1, falseEast,
3612 : falseNorth);
3613 :
3614 : /* Point Perspective / AZP projection */
3615 : }
3616 0 : else if (strcmp(pstr, "AZP") == 0)
3617 : {
3618 0 : projName.assign("perspective_point_height_");
3619 0 : m_oSRS.SetProjection(SRS_PP_PERSPECTIVE_POINT_HEIGHT);
3620 : /* # appears to need height... maybe center lon/lat */
3621 :
3622 : /* Polar Stereographic / STG projection */
3623 : }
3624 0 : else if (strcmp(pstr, "STG") == 0)
3625 : {
3626 0 : projName.assign("Polar_Stereographic_");
3627 0 : m_oSRS.SetStereographic(crval2, crval1, scale, falseEast,
3628 : falseNorth);
3629 : }
3630 : else
3631 : {
3632 0 : CPLError(CE_Failure, CPLE_AppDefined,
3633 : "Unknown projection.");
3634 : }
3635 :
3636 16 : projName.append(target);
3637 16 : m_oSRS.SetProjParm(SRS_PP_FALSE_EASTING, 0.0);
3638 16 : m_oSRS.SetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
3639 :
3640 16 : m_oSRS.SetNode("PROJCS", projName.c_str());
3641 :
3642 16 : m_oSRS.SetGeogCS(GeogName.c_str(), DatumName.c_str(), target,
3643 : aRadius, invFlattening, "Reference_Meridian",
3644 : 0.0, "degree", 0.0174532925199433);
3645 : }
3646 : else
3647 : {
3648 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown projection.");
3649 : }
3650 : }
3651 : }
3652 : else
3653 : {
3654 17 : CPLError(CE_Warning, CPLE_AppDefined,
3655 : "No CTYPE keywords: no geospatial information available.");
3656 : }
3657 : }
3658 :
3659 : /************************************************************************/
3660 : /* LoadFITSInfo() */
3661 : /************************************************************************/
3662 :
3663 42 : void FITSDataset::LoadFITSInfo()
3664 :
3665 : {
3666 42 : int status = 0;
3667 : int bitpix;
3668 : double dfScale, dfOffset;
3669 :
3670 42 : LoadGeoreferencing();
3671 :
3672 42 : CPLAssert(!m_bMetadataChanged);
3673 42 : CPLAssert(!m_bNoDataChanged);
3674 :
3675 42 : m_bMetadataChanged = false;
3676 42 : m_bNoDataChanged = false;
3677 :
3678 42 : bitpix = this->m_fitsDataType;
3679 42 : FITSRasterBand *poBand = cpl::down_cast<FITSRasterBand *>(GetRasterBand(1));
3680 :
3681 42 : if (bitpix != TUSHORT && bitpix != TUINT)
3682 : {
3683 36 : fits_read_key(m_hFITS, TDOUBLE, "BSCALE", &dfScale, nullptr, &status);
3684 36 : if (status)
3685 : {
3686 34 : status = 0;
3687 34 : dfScale = 1.;
3688 : }
3689 36 : fits_read_key(m_hFITS, TDOUBLE, "BZERO", &dfOffset, nullptr, &status);
3690 36 : if (status)
3691 : {
3692 34 : status = 0;
3693 34 : dfOffset = 0.;
3694 : }
3695 36 : if (dfScale != 1. || dfOffset != 0.)
3696 : {
3697 2 : poBand->m_bHaveOffsetScale = true;
3698 2 : poBand->m_dfScale = dfScale;
3699 2 : poBand->m_dfOffset = dfOffset;
3700 : }
3701 : }
3702 :
3703 42 : fits_read_key(m_hFITS, TDOUBLE, "BLANK", &m_dfNoDataValue, nullptr,
3704 : &status);
3705 42 : m_bNoDataSet = !status;
3706 42 : }
3707 :
3708 : /************************************************************************/
3709 : /* GDALRegister_FITS() */
3710 : /************************************************************************/
3711 :
3712 9 : void GDALRegister_FITS()
3713 :
3714 : {
3715 9 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
3716 0 : return;
3717 :
3718 9 : GDALDriver *poDriver = new GDALDriver();
3719 9 : FITSDriverSetCommonMetadata(poDriver);
3720 :
3721 9 : poDriver->pfnOpen = FITSDataset::Open;
3722 9 : poDriver->pfnCreate = FITSDataset::Create;
3723 9 : poDriver->pfnDelete = FITSDataset::Delete;
3724 :
3725 9 : GetGDALDriverManager()->RegisterDriver(poDriver);
3726 : }
|