LCOV - code coverage report
Current view: top level - frmts/fits - fitsdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1315 1643 80.0 %
Date: 2024-11-21 22:18:42 Functions: 70 85 82.4 %

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

Generated by: LCOV version 1.14