LCOV - code coverage report
Current view: top level - frmts/raw - mffdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 345 466 74.0 %
Date: 2025-03-28 21:34:50 Functions: 15 20 75.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GView
       4             :  * Purpose:  Implementation of Atlantis MFF Support
       5             :  * Author:   Frank Warmerdam, warmerda@home.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2000, Frank Warmerdam
       9             :  * Copyright (c) 2008-2012, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "atlsci_spheroid.h"
      15             : #include "cpl_string.h"
      16             : #include "gdal_frmts.h"
      17             : #include "ogr_spatialref.h"
      18             : #include "rawdataset.h"
      19             : 
      20             : #include <cctype>
      21             : #include <cmath>
      22             : #include <algorithm>
      23             : 
      24             : enum
      25             : {
      26             :     MFFPRJ_NONE,
      27             :     MFFPRJ_LL,
      28             :     MFFPRJ_UTM,
      29             :     MFFPRJ_UNRECOGNIZED
      30             : };
      31             : 
      32             : /************************************************************************/
      33             : /* ==================================================================== */
      34             : /*                              MFFDataset                              */
      35             : /* ==================================================================== */
      36             : /************************************************************************/
      37             : 
      38             : class MFFDataset final : public RawDataset
      39             : {
      40             :     int nGCPCount;
      41             :     GDAL_GCP *pasGCPList;
      42             : 
      43             :     OGRSpatialReference m_oSRS{};
      44             :     OGRSpatialReference m_oGCPSRS{};
      45             :     double adfGeoTransform[6];
      46             :     char **m_papszFileList;
      47             : 
      48             :     void ScanForGCPs();
      49             :     void ScanForProjectionInfo();
      50             : 
      51             :     CPL_DISALLOW_COPY_ASSIGN(MFFDataset)
      52             : 
      53             :     CPLErr Close() override;
      54             : 
      55             :   public:
      56             :     MFFDataset();
      57             :     ~MFFDataset() override;
      58             : 
      59             :     char **papszHdrLines;
      60             : 
      61             :     VSILFILE **pafpBandFiles;
      62             : 
      63             :     char **GetFileList() override;
      64             : 
      65             :     int GetGCPCount() override;
      66             : 
      67           0 :     const OGRSpatialReference *GetGCPSpatialRef() const override
      68             :     {
      69           0 :         return m_oGCPSRS.IsEmpty() ? nullptr : &m_oGCPSRS;
      70             :     }
      71             : 
      72             :     const GDAL_GCP *GetGCPs() override;
      73             : 
      74           0 :     const OGRSpatialReference *GetSpatialRef() const override
      75             :     {
      76           0 :         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
      77             :     }
      78             : 
      79             :     CPLErr GetGeoTransform(double *) override;
      80             : 
      81             :     static GDALDataset *Open(GDALOpenInfo *);
      82             : };
      83             : 
      84             : /************************************************************************/
      85             : /* ==================================================================== */
      86             : /*                            MFFTiledBand                              */
      87             : /* ==================================================================== */
      88             : /************************************************************************/
      89             : 
      90             : class MFFTiledBand final : public GDALRasterBand
      91             : {
      92             :     friend class MFFDataset;
      93             : 
      94             :     VSILFILE *fpRaw;
      95             :     RawRasterBand::ByteOrder eByteOrder;
      96             : 
      97             :     CPL_DISALLOW_COPY_ASSIGN(MFFTiledBand)
      98             : 
      99             :   public:
     100             :     MFFTiledBand(MFFDataset *, int, VSILFILE *, int, int, GDALDataType,
     101             :                  RawRasterBand::ByteOrder);
     102             :     ~MFFTiledBand() override;
     103             : 
     104             :     CPLErr IReadBlock(int, int, void *) override;
     105             : };
     106             : 
     107             : /************************************************************************/
     108             : /*                            MFFTiledBand()                            */
     109             : /************************************************************************/
     110             : 
     111           2 : MFFTiledBand::MFFTiledBand(MFFDataset *poDSIn, int nBandIn, VSILFILE *fp,
     112             :                            int nTileXSize, int nTileYSize,
     113             :                            GDALDataType eDataTypeIn,
     114           2 :                            RawRasterBand::ByteOrder eByteOrderIn)
     115           2 :     : fpRaw(fp), eByteOrder(eByteOrderIn)
     116             : {
     117           2 :     poDS = poDSIn;
     118           2 :     nBand = nBandIn;
     119             : 
     120           2 :     eDataType = eDataTypeIn;
     121             : 
     122           2 :     nBlockXSize = nTileXSize;
     123           2 :     nBlockYSize = nTileYSize;
     124           2 : }
     125             : 
     126             : /************************************************************************/
     127             : /*                           ~MFFTiledBand()                            */
     128             : /************************************************************************/
     129             : 
     130           4 : MFFTiledBand::~MFFTiledBand()
     131             : 
     132             : {
     133           2 :     if (VSIFCloseL(fpRaw) != 0)
     134             :     {
     135           0 :         CPLError(CE_Failure, CPLE_FileIO, "I/O error");
     136             :     }
     137           4 : }
     138             : 
     139             : /************************************************************************/
     140             : /*                             IReadBlock()                             */
     141             : /************************************************************************/
     142             : 
     143           1 : CPLErr MFFTiledBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
     144             : 
     145             : {
     146           1 :     const int nTilesPerRow = (nRasterXSize + nBlockXSize - 1) / nBlockXSize;
     147           1 :     const int nWordSize = GDALGetDataTypeSize(eDataType) / 8;
     148           1 :     const int nBlockSize = nWordSize * nBlockXSize * nBlockYSize;
     149             : 
     150           1 :     const vsi_l_offset nOffset =
     151           1 :         nBlockSize *
     152           1 :         (nBlockXOff + static_cast<vsi_l_offset>(nBlockYOff) * nTilesPerRow);
     153             : 
     154           2 :     if (VSIFSeekL(fpRaw, nOffset, SEEK_SET) == -1 ||
     155           1 :         VSIFReadL(pImage, 1, nBlockSize, fpRaw) < 1)
     156             :     {
     157           0 :         CPLError(CE_Failure, CPLE_FileIO,
     158             :                  "Read of tile %d/%d failed with fseek or fread error.",
     159             :                  nBlockXOff, nBlockYOff);
     160           0 :         return CE_Failure;
     161             :     }
     162             : 
     163           1 :     if (eByteOrder != RawRasterBand::NATIVE_BYTE_ORDER && nWordSize > 1)
     164             :     {
     165           0 :         if (GDALDataTypeIsComplex(eDataType))
     166             :         {
     167           0 :             GDALSwapWords(pImage, nWordSize / 2, nBlockXSize * nBlockYSize,
     168             :                           nWordSize);
     169           0 :             GDALSwapWords(reinterpret_cast<GByte *>(pImage) + nWordSize / 2,
     170           0 :                           nWordSize / 2, nBlockXSize * nBlockYSize, nWordSize);
     171             :         }
     172             :         else
     173           0 :             GDALSwapWords(pImage, nWordSize, nBlockXSize * nBlockYSize,
     174             :                           nWordSize);
     175             :     }
     176             : 
     177           1 :     return CE_None;
     178             : }
     179             : 
     180             : /************************************************************************/
     181             : /*                      MFF Spheroids                                   */
     182             : /************************************************************************/
     183             : 
     184             : class MFFSpheroidList : public SpheroidList
     185             : {
     186             :   public:
     187             :     MFFSpheroidList();
     188             : 
     189           6 :     ~MFFSpheroidList()
     190           6 :     {
     191           6 :     }
     192             : };
     193             : 
     194           6 : MFFSpheroidList ::MFFSpheroidList()
     195             : {
     196           6 :     num_spheroids = 18;
     197             : 
     198           6 :     epsilonR = 0.1;
     199           6 :     epsilonI = 0.000001;
     200             : 
     201           6 :     spheroids[0].SetValuesByRadii("SPHERE", 6371007.0, 6371007.0);
     202           6 :     spheroids[1].SetValuesByRadii("EVEREST", 6377304.0, 6356103.0);
     203           6 :     spheroids[2].SetValuesByRadii("BESSEL", 6377397.0, 6356082.0);
     204           6 :     spheroids[3].SetValuesByRadii("AIRY", 6377563.0, 6356300.0);
     205           6 :     spheroids[4].SetValuesByRadii("CLARKE_1858", 6378294.0, 6356621.0);
     206           6 :     spheroids[5].SetValuesByRadii("CLARKE_1866", 6378206.4, 6356583.8);
     207           6 :     spheroids[6].SetValuesByRadii("CLARKE_1880", 6378249.0, 6356517.0);
     208           6 :     spheroids[7].SetValuesByRadii("HAYFORD", 6378388.0, 6356915.0);
     209           6 :     spheroids[8].SetValuesByRadii("KRASOVSKI", 6378245.0, 6356863.0);
     210           6 :     spheroids[9].SetValuesByRadii("HOUGH", 6378270.0, 6356794.0);
     211           6 :     spheroids[10].SetValuesByRadii("FISHER_60", 6378166.0, 6356784.0);
     212           6 :     spheroids[11].SetValuesByRadii("KAULA", 6378165.0, 6356345.0);
     213           6 :     spheroids[12].SetValuesByRadii("IUGG_67", 6378160.0, 6356775.0);
     214           6 :     spheroids[13].SetValuesByRadii("FISHER_68", 6378150.0, 6356330.0);
     215           6 :     spheroids[14].SetValuesByRadii("WGS_72", 6378135.0, 6356751.0);
     216           6 :     spheroids[15].SetValuesByRadii("IUGG_75", 6378140.0, 6356755.0);
     217           6 :     spheroids[16].SetValuesByRadii("WGS_84", 6378137.0, 6356752.0);
     218           6 :     spheroids[17].SetValuesByRadii("HUGHES", 6378273.0, 6356889.4);
     219           6 : }
     220             : 
     221             : /************************************************************************/
     222             : /* ==================================================================== */
     223             : /*                              MFFDataset                              */
     224             : /* ==================================================================== */
     225             : /************************************************************************/
     226             : 
     227             : /************************************************************************/
     228             : /*                            MFFDataset()                             */
     229             : /************************************************************************/
     230             : 
     231           7 : MFFDataset::MFFDataset()
     232             :     : nGCPCount(0), pasGCPList(nullptr), m_papszFileList(nullptr),
     233           7 :       papszHdrLines(nullptr), pafpBandFiles(nullptr)
     234             : {
     235           7 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     236           7 :     m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     237           7 :     adfGeoTransform[0] = 0.0;
     238           7 :     adfGeoTransform[1] = 1.0;
     239           7 :     adfGeoTransform[2] = 0.0;
     240           7 :     adfGeoTransform[3] = 0.0;
     241           7 :     adfGeoTransform[4] = 0.0;
     242           7 :     adfGeoTransform[5] = 1.0;
     243           7 : }
     244             : 
     245             : /************************************************************************/
     246             : /*                            ~MFFDataset()                            */
     247             : /************************************************************************/
     248             : 
     249          14 : MFFDataset::~MFFDataset()
     250             : 
     251             : {
     252           7 :     MFFDataset::Close();
     253          14 : }
     254             : 
     255             : /************************************************************************/
     256             : /*                              Close()                                 */
     257             : /************************************************************************/
     258             : 
     259          14 : CPLErr MFFDataset::Close()
     260             : {
     261          14 :     CPLErr eErr = CE_None;
     262          14 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     263             :     {
     264           7 :         if (MFFDataset::FlushCache(true) != CE_None)
     265           0 :             eErr = CE_Failure;
     266             : 
     267           7 :         CSLDestroy(papszHdrLines);
     268           7 :         if (pafpBandFiles)
     269             :         {
     270           0 :             for (int i = 0; i < GetRasterCount(); i++)
     271             :             {
     272           0 :                 if (pafpBandFiles[i])
     273             :                 {
     274           0 :                     if (VSIFCloseL(pafpBandFiles[i]) != 0)
     275             :                     {
     276           0 :                         eErr = CE_Failure;
     277           0 :                         CPLError(CE_Failure, CPLE_FileIO, "I/O error");
     278             :                     }
     279             :                 }
     280             :             }
     281           0 :             CPLFree(pafpBandFiles);
     282             :         }
     283             : 
     284           7 :         if (nGCPCount > 0)
     285             :         {
     286           6 :             GDALDeinitGCPs(nGCPCount, pasGCPList);
     287             :         }
     288           7 :         CPLFree(pasGCPList);
     289           7 :         CSLDestroy(m_papszFileList);
     290             : 
     291           7 :         if (GDALPamDataset::Close() != CE_None)
     292           0 :             eErr = CE_Failure;
     293             :     }
     294          14 :     return eErr;
     295             : }
     296             : 
     297             : /************************************************************************/
     298             : /*                            GetFileList()                             */
     299             : /************************************************************************/
     300             : 
     301           3 : char **MFFDataset::GetFileList()
     302             : {
     303           3 :     char **papszFileList = RawDataset::GetFileList();
     304           3 :     papszFileList = CSLInsertStrings(papszFileList, -1, m_papszFileList);
     305           3 :     return papszFileList;
     306             : }
     307             : 
     308             : /************************************************************************/
     309             : /*                            GetGCPCount()                             */
     310             : /************************************************************************/
     311             : 
     312           0 : int MFFDataset::GetGCPCount()
     313             : 
     314             : {
     315           0 :     return nGCPCount;
     316             : }
     317             : 
     318             : /************************************************************************/
     319             : /*                          GetGeoTransform()                           */
     320             : /************************************************************************/
     321             : 
     322           0 : CPLErr MFFDataset::GetGeoTransform(double *padfTransform)
     323             : 
     324             : {
     325           0 :     memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
     326           0 :     return CE_None;
     327             : }
     328             : 
     329             : /************************************************************************/
     330             : /*                               GetGCP()                               */
     331             : /************************************************************************/
     332             : 
     333           0 : const GDAL_GCP *MFFDataset::GetGCPs()
     334             : 
     335             : {
     336           0 :     return pasGCPList;
     337             : }
     338             : 
     339             : /************************************************************************/
     340             : /*                            ScanForGCPs()                             */
     341             : /************************************************************************/
     342             : 
     343           7 : void MFFDataset::ScanForGCPs()
     344             : 
     345             : {
     346           7 :     int NUM_GCPS = 0;
     347             : 
     348           7 :     if (CSLFetchNameValue(papszHdrLines, "NUM_GCPS") != nullptr)
     349           4 :         NUM_GCPS = atoi(CSLFetchNameValue(papszHdrLines, "NUM_GCPS"));
     350           7 :     if (NUM_GCPS < 0)
     351           0 :         return;
     352             : 
     353           7 :     nGCPCount = 0;
     354           7 :     pasGCPList =
     355           7 :         static_cast<GDAL_GCP *>(VSICalloc(sizeof(GDAL_GCP), 5 + NUM_GCPS));
     356           7 :     if (pasGCPList == nullptr)
     357           0 :         return;
     358             : 
     359          42 :     for (int nCorner = 0; nCorner < 5; nCorner++)
     360             :     {
     361          35 :         const char *pszBase = nullptr;
     362          35 :         double dfRasterX = 0.0;
     363          35 :         double dfRasterY = 0.0;
     364             : 
     365          35 :         if (nCorner == 0)
     366             :         {
     367           7 :             dfRasterX = 0.5;
     368           7 :             dfRasterY = 0.5;
     369           7 :             pszBase = "TOP_LEFT_CORNER";
     370             :         }
     371          28 :         else if (nCorner == 1)
     372             :         {
     373           7 :             dfRasterX = GetRasterXSize() - 0.5;
     374           7 :             dfRasterY = 0.5;
     375           7 :             pszBase = "TOP_RIGHT_CORNER";
     376             :         }
     377          21 :         else if (nCorner == 2)
     378             :         {
     379           7 :             dfRasterX = GetRasterXSize() - 0.5;
     380           7 :             dfRasterY = GetRasterYSize() - 0.5;
     381           7 :             pszBase = "BOTTOM_RIGHT_CORNER";
     382             :         }
     383          14 :         else if (nCorner == 3)
     384             :         {
     385           7 :             dfRasterX = 0.5;
     386           7 :             dfRasterY = GetRasterYSize() - 0.5;
     387           7 :             pszBase = "BOTTOM_LEFT_CORNER";
     388             :         }
     389             :         else /* if( nCorner == 4 ) */
     390             :         {
     391           7 :             dfRasterX = GetRasterXSize() / 2.0;
     392           7 :             dfRasterY = GetRasterYSize() / 2.0;
     393           7 :             pszBase = "CENTRE";
     394             :         }
     395             : 
     396          35 :         char szLatName[40] = {'\0'};
     397          35 :         char szLongName[40] = {'\0'};
     398          35 :         snprintf(szLatName, sizeof(szLatName), "%s_LATITUDE", pszBase);
     399          35 :         snprintf(szLongName, sizeof(szLongName), "%s_LONGITUDE", pszBase);
     400             : 
     401          45 :         if (CSLFetchNameValue(papszHdrLines, szLatName) != nullptr &&
     402          10 :             CSLFetchNameValue(papszHdrLines, szLongName) != nullptr)
     403             :         {
     404          10 :             GDALInitGCPs(1, pasGCPList + nGCPCount);
     405             : 
     406          10 :             CPLFree(pasGCPList[nGCPCount].pszId);
     407             : 
     408          10 :             pasGCPList[nGCPCount].pszId = CPLStrdup(pszBase);
     409             : 
     410          20 :             pasGCPList[nGCPCount].dfGCPX =
     411          10 :                 CPLAtof(CSLFetchNameValue(papszHdrLines, szLongName));
     412          20 :             pasGCPList[nGCPCount].dfGCPY =
     413          10 :                 CPLAtof(CSLFetchNameValue(papszHdrLines, szLatName));
     414          10 :             pasGCPList[nGCPCount].dfGCPZ = 0.0;
     415             : 
     416          10 :             pasGCPList[nGCPCount].dfGCPPixel = dfRasterX;
     417          10 :             pasGCPList[nGCPCount].dfGCPLine = dfRasterY;
     418             : 
     419          10 :             nGCPCount++;
     420             :         }
     421             :     }
     422             : 
     423             :     /* -------------------------------------------------------------------- */
     424             :     /*      Collect standalone GCPs.  They look like:                       */
     425             :     /*                                                                      */
     426             :     /*      GCPn = row, col, lat, long                                      */
     427             :     /*      GCP1 = 1, 1, 45.0, -75.0                                        */
     428             :     /* -------------------------------------------------------------------- */
     429          11 :     for (int i = 0; i < NUM_GCPS; i++)
     430             :     {
     431           4 :         char szName[25] = {'\0'};
     432           4 :         snprintf(szName, sizeof(szName), "GCP%d", i + 1);
     433           4 :         if (CSLFetchNameValue(papszHdrLines, szName) == nullptr)
     434           0 :             continue;
     435             : 
     436           4 :         char **papszTokens = CSLTokenizeStringComplex(
     437           4 :             CSLFetchNameValue(papszHdrLines, szName), ",", FALSE, FALSE);
     438           4 :         if (CSLCount(papszTokens) == 4)
     439             :         {
     440           4 :             GDALInitGCPs(1, pasGCPList + nGCPCount);
     441             : 
     442           4 :             CPLFree(pasGCPList[nGCPCount].pszId);
     443           4 :             pasGCPList[nGCPCount].pszId = CPLStrdup(szName);
     444             : 
     445           4 :             pasGCPList[nGCPCount].dfGCPX = CPLAtof(papszTokens[3]);
     446           4 :             pasGCPList[nGCPCount].dfGCPY = CPLAtof(papszTokens[2]);
     447           4 :             pasGCPList[nGCPCount].dfGCPZ = 0.0;
     448           4 :             pasGCPList[nGCPCount].dfGCPPixel = CPLAtof(papszTokens[1]) + 0.5;
     449           4 :             pasGCPList[nGCPCount].dfGCPLine = CPLAtof(papszTokens[0]) + 0.5;
     450             : 
     451           4 :             nGCPCount++;
     452             :         }
     453             : 
     454           4 :         CSLDestroy(papszTokens);
     455             :     }
     456             : }
     457             : 
     458             : /************************************************************************/
     459             : /*                        ScanForProjectionInfo                         */
     460             : /************************************************************************/
     461             : 
     462           7 : void MFFDataset::ScanForProjectionInfo()
     463             : {
     464             :     const char *pszProjName =
     465           7 :         CSLFetchNameValue(papszHdrLines, "PROJECTION_NAME");
     466             :     const char *pszOriginLong =
     467           7 :         CSLFetchNameValue(papszHdrLines, "PROJECTION_ORIGIN_LONGITUDE");
     468             :     const char *pszSpheroidName =
     469           7 :         CSLFetchNameValue(papszHdrLines, "SPHEROID_NAME");
     470             : 
     471           7 :     if (pszProjName == nullptr)
     472             :     {
     473           1 :         m_oSRS.Clear();
     474           1 :         m_oGCPSRS.Clear();
     475           1 :         return;
     476             :     }
     477           6 :     else if ((!EQUAL(pszProjName, "utm")) && (!EQUAL(pszProjName, "ll")))
     478             :     {
     479           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     480             :                  "Only utm and lat/long projections are currently supported.");
     481           0 :         m_oSRS.Clear();
     482           0 :         m_oGCPSRS.Clear();
     483           0 :         return;
     484             :     }
     485           6 :     MFFSpheroidList *mffEllipsoids = new MFFSpheroidList;
     486             : 
     487          12 :     OGRSpatialReference oProj;
     488           6 :     oProj.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     489           6 :     if (EQUAL(pszProjName, "utm"))
     490             :     {
     491             :         int nZone;
     492             : 
     493           6 :         if (pszOriginLong == nullptr)
     494             :         {
     495             :             // If origin not specified, assume 0.0.
     496           4 :             CPLError(
     497             :                 CE_Warning, CPLE_AppDefined,
     498             :                 "No projection origin longitude specified.  Assuming 0.0.");
     499           4 :             nZone = 31;
     500             :         }
     501             :         else
     502           2 :             nZone = 31 + static_cast<int>(floor(CPLAtof(pszOriginLong) / 6.0));
     503             : 
     504           6 :         if (nGCPCount >= 5 && pasGCPList[4].dfGCPY < 0)
     505           0 :             oProj.SetUTM(nZone, 0);
     506             :         else
     507           6 :             oProj.SetUTM(nZone, 1);
     508             : 
     509           6 :         if (pszOriginLong != nullptr)
     510           2 :             oProj.SetProjParm(SRS_PP_CENTRAL_MERIDIAN, CPLAtof(pszOriginLong));
     511             :     }
     512             : 
     513          12 :     OGRSpatialReference oLL;
     514           6 :     oLL.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     515           6 :     if (pszOriginLong != nullptr)
     516           2 :         oLL.SetProjParm(SRS_PP_LONGITUDE_OF_ORIGIN, CPLAtof(pszOriginLong));
     517             : 
     518           6 :     if (pszSpheroidName == nullptr)
     519             :     {
     520           4 :         CPLError(CE_Warning, CPLE_AppDefined,
     521             :                  "Unspecified ellipsoid.  Using wgs-84 parameters.\n");
     522             : 
     523           4 :         oProj.SetWellKnownGeogCS("WGS84");
     524           4 :         oLL.SetWellKnownGeogCS("WGS84");
     525             :     }
     526             :     else
     527             :     {
     528           2 :         if (mffEllipsoids->SpheroidInList(pszSpheroidName))
     529             :         {
     530           2 :             oProj.SetGeogCS(
     531             :                 "unknown", "unknown", pszSpheroidName,
     532             :                 mffEllipsoids->GetSpheroidEqRadius(pszSpheroidName),
     533             :                 mffEllipsoids->GetSpheroidInverseFlattening(pszSpheroidName));
     534           2 :             oLL.SetGeogCS(
     535             :                 "unknown", "unknown", pszSpheroidName,
     536             :                 mffEllipsoids->GetSpheroidEqRadius(pszSpheroidName),
     537             :                 mffEllipsoids->GetSpheroidInverseFlattening(pszSpheroidName));
     538             :         }
     539           0 :         else if (EQUAL(pszSpheroidName, "USER_DEFINED"))
     540             :         {
     541             :             const char *pszSpheroidEqRadius =
     542           0 :                 CSLFetchNameValue(papszHdrLines, "SPHEROID_EQUATORIAL_RADIUS");
     543             :             const char *pszSpheroidPolarRadius =
     544           0 :                 CSLFetchNameValue(papszHdrLines, "SPHEROID_POLAR_RADIUS");
     545           0 :             if ((pszSpheroidEqRadius != nullptr) &&
     546             :                 (pszSpheroidPolarRadius != nullptr))
     547             :             {
     548           0 :                 const double eq_radius = CPLAtof(pszSpheroidEqRadius);
     549           0 :                 const double polar_radius = CPLAtof(pszSpheroidPolarRadius);
     550           0 :                 oProj.SetGeogCS("unknown", "unknown", "unknown", eq_radius,
     551           0 :                                 eq_radius / (eq_radius - polar_radius));
     552           0 :                 oLL.SetGeogCS("unknown", "unknown", "unknown", eq_radius,
     553           0 :                               eq_radius / (eq_radius - polar_radius));
     554             :             }
     555             :             else
     556             :             {
     557           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     558             :                          "Radii not specified for user-defined ellipsoid. "
     559             :                          "Using wgs-84 parameters.");
     560           0 :                 oProj.SetWellKnownGeogCS("WGS84");
     561           0 :                 oLL.SetWellKnownGeogCS("WGS84");
     562             :             }
     563             :         }
     564             :         else
     565             :         {
     566           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     567             :                      "Unrecognized ellipsoid.  Using wgs-84 parameters.");
     568           0 :             oProj.SetWellKnownGeogCS("WGS84");
     569           0 :             oLL.SetWellKnownGeogCS("WGS84");
     570             :         }
     571             :     }
     572             : 
     573             :     /* If a geotransform is sufficient to represent the GCP's (i.e. each */
     574             :     /* estimated gcp is within 0.25*pixel size of the actual value- this */
     575             :     /* is the test applied by GDALGCPsToGeoTransform), store the         */
     576             :     /* geotransform.                                                     */
     577           6 :     bool transform_ok = false;
     578             : 
     579           6 :     if (EQUAL(pszProjName, "LL"))
     580             :     {
     581           0 :         transform_ok = CPL_TO_BOOL(
     582           0 :             GDALGCPsToGeoTransform(nGCPCount, pasGCPList, adfGeoTransform, 0));
     583             :     }
     584             :     else
     585             :     {
     586             :         OGRCoordinateTransformation *poTransform =
     587           6 :             OGRCreateCoordinateTransformation(&oLL, &oProj);
     588           6 :         bool bSuccess = true;
     589           6 :         if (poTransform == nullptr)
     590             :         {
     591           0 :             CPLErrorReset();
     592           0 :             bSuccess = FALSE;
     593             :         }
     594             : 
     595             :         double *dfPrjX =
     596           6 :             static_cast<double *>(CPLMalloc(nGCPCount * sizeof(double)));
     597             :         double *dfPrjY =
     598           6 :             static_cast<double *>(CPLMalloc(nGCPCount * sizeof(double)));
     599             : 
     600          20 :         for (int gcp_index = 0; gcp_index < nGCPCount; gcp_index++)
     601             :         {
     602          14 :             dfPrjX[gcp_index] = pasGCPList[gcp_index].dfGCPX;
     603          14 :             dfPrjY[gcp_index] = pasGCPList[gcp_index].dfGCPY;
     604             : 
     605          28 :             if (bSuccess && !poTransform->Transform(1, &(dfPrjX[gcp_index]),
     606          14 :                                                     &(dfPrjY[gcp_index])))
     607           0 :                 bSuccess = FALSE;
     608             :         }
     609             : 
     610           6 :         if (bSuccess)
     611             :         {
     612          20 :             for (int gcp_index = 0; gcp_index < nGCPCount; gcp_index++)
     613             :             {
     614          14 :                 pasGCPList[gcp_index].dfGCPX = dfPrjX[gcp_index];
     615          14 :                 pasGCPList[gcp_index].dfGCPY = dfPrjY[gcp_index];
     616             :             }
     617           6 :             transform_ok = CPL_TO_BOOL(GDALGCPsToGeoTransform(
     618           6 :                 nGCPCount, pasGCPList, adfGeoTransform, 0));
     619             :         }
     620             : 
     621           6 :         if (poTransform)
     622           6 :             delete poTransform;
     623             : 
     624           6 :         CPLFree(dfPrjX);
     625           6 :         CPLFree(dfPrjY);
     626             :     }
     627             : 
     628           6 :     m_oSRS = oProj;
     629           6 :     m_oGCPSRS = std::move(oProj);
     630             : 
     631           6 :     if (!transform_ok)
     632             :     {
     633             :         /* transform is sufficient in some cases (slant range, standalone gcps)
     634             :          */
     635           4 :         adfGeoTransform[0] = 0.0;
     636           4 :         adfGeoTransform[1] = 1.0;
     637           4 :         adfGeoTransform[2] = 0.0;
     638           4 :         adfGeoTransform[3] = 0.0;
     639           4 :         adfGeoTransform[4] = 0.0;
     640           4 :         adfGeoTransform[5] = 1.0;
     641           4 :         m_oSRS.Clear();
     642             :     }
     643             : 
     644           6 :     delete mffEllipsoids;
     645             : }
     646             : 
     647             : /************************************************************************/
     648             : /*                                Open()                                */
     649             : /************************************************************************/
     650             : 
     651       29536 : GDALDataset *MFFDataset::Open(GDALOpenInfo *poOpenInfo)
     652             : 
     653             : {
     654             :     /* -------------------------------------------------------------------- */
     655             :     /*      We assume the user is pointing to the header file.              */
     656             :     /* -------------------------------------------------------------------- */
     657       29536 :     if (poOpenInfo->nHeaderBytes < 17 || poOpenInfo->fpL == nullptr)
     658       26352 :         return nullptr;
     659             : 
     660        3184 :     if (!poOpenInfo->IsExtensionEqualToCI("hdr"))
     661        3167 :         return nullptr;
     662             : 
     663             :     /* -------------------------------------------------------------------- */
     664             :     /*      Load the .hdr file, and compress white space out around the     */
     665             :     /*      equal sign.                                                     */
     666             :     /* -------------------------------------------------------------------- */
     667          17 :     char **papszHdrLines = CSLLoad(poOpenInfo->pszFilename);
     668          17 :     if (papszHdrLines == nullptr)
     669           0 :         return nullptr;
     670             : 
     671             :     // Remove spaces.  e.g.
     672             :     // SPHEROID_NAME = CLARKE_1866 -> SPHEROID_NAME=CLARKE_1866
     673         567 :     for (int i = 0; papszHdrLines[i] != nullptr; i++)
     674             :     {
     675         550 :         int iDst = 0;
     676         550 :         char *pszLine = papszHdrLines[i];
     677             : 
     678       16139 :         for (int iSrc = 0; pszLine[iSrc] != '\0'; iSrc++)
     679             :         {
     680       15589 :             if (pszLine[iSrc] != ' ')
     681             :             {
     682       13635 :                 pszLine[iDst++] = pszLine[iSrc];
     683             :             }
     684             :         }
     685         550 :         pszLine[iDst] = '\0';
     686             :     }
     687             : 
     688             :     /* -------------------------------------------------------------------- */
     689             :     /*      Verify it is an MFF file.                                       */
     690             :     /* -------------------------------------------------------------------- */
     691          24 :     if (CSLFetchNameValue(papszHdrLines, "IMAGE_FILE_FORMAT") != nullptr &&
     692           7 :         !EQUAL(CSLFetchNameValue(papszHdrLines, "IMAGE_FILE_FORMAT"), "MFF"))
     693             :     {
     694           0 :         CSLDestroy(papszHdrLines);
     695           0 :         return nullptr;
     696             :     }
     697             : 
     698          17 :     if ((CSLFetchNameValue(papszHdrLines, "IMAGE_LINES") == nullptr ||
     699          27 :          CSLFetchNameValue(papszHdrLines, "LINE_SAMPLES") == nullptr) &&
     700          10 :         (CSLFetchNameValue(papszHdrLines, "no_rows") == nullptr ||
     701           0 :          CSLFetchNameValue(papszHdrLines, "no_columns") == nullptr))
     702             :     {
     703          10 :         CSLDestroy(papszHdrLines);
     704          10 :         return nullptr;
     705             :     }
     706             : 
     707             :     /* -------------------------------------------------------------------- */
     708             :     /*      Create a corresponding GDALDataset.                             */
     709             :     /* -------------------------------------------------------------------- */
     710          14 :     auto poDS = std::make_unique<MFFDataset>();
     711             : 
     712           7 :     poDS->papszHdrLines = papszHdrLines;
     713             : 
     714           7 :     poDS->eAccess = poOpenInfo->eAccess;
     715             : 
     716             :     /* -------------------------------------------------------------------- */
     717             :     /*      Set some dataset wide information.                              */
     718             :     /* -------------------------------------------------------------------- */
     719           9 :     if (CSLFetchNameValue(papszHdrLines, "no_rows") != nullptr &&
     720           2 :         CSLFetchNameValue(papszHdrLines, "no_columns") != nullptr)
     721             :     {
     722           0 :         poDS->nRasterXSize =
     723           0 :             atoi(CSLFetchNameValue(papszHdrLines, "no_columns"));
     724           0 :         poDS->nRasterYSize = atoi(CSLFetchNameValue(papszHdrLines, "no_rows"));
     725             :     }
     726             :     else
     727             :     {
     728          14 :         poDS->nRasterXSize =
     729           7 :             atoi(CSLFetchNameValue(papszHdrLines, "LINE_SAMPLES"));
     730           7 :         poDS->nRasterYSize =
     731           7 :             atoi(CSLFetchNameValue(papszHdrLines, "IMAGE_LINES"));
     732             :     }
     733             : 
     734           7 :     if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
     735             :     {
     736           0 :         return nullptr;
     737             :     }
     738             : 
     739           7 :     RawRasterBand::ByteOrder eByteOrder = RawRasterBand::NATIVE_BYTE_ORDER;
     740             : 
     741           7 :     const char *pszByteOrder = CSLFetchNameValue(papszHdrLines, "BYTE_ORDER");
     742           7 :     if (pszByteOrder)
     743             :     {
     744           3 :         eByteOrder = EQUAL(pszByteOrder, "LSB")
     745           3 :                          ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
     746             :                          : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN;
     747             :     }
     748             : 
     749             :     /* -------------------------------------------------------------------- */
     750             :     /*      Get some information specific to APP tiled files.               */
     751             :     /* -------------------------------------------------------------------- */
     752           7 :     int nTileXSize = 0;
     753           7 :     int nTileYSize = 0;
     754           7 :     const char *pszRefinedType = CSLFetchNameValue(papszHdrLines, "type");
     755           7 :     const bool bTiled = CSLFetchNameValue(papszHdrLines, "no_rows") != nullptr;
     756             : 
     757           7 :     if (bTiled)
     758             :     {
     759           2 :         if (CSLFetchNameValue(papszHdrLines, "tile_size_rows"))
     760           2 :             nTileYSize =
     761           2 :                 atoi(CSLFetchNameValue(papszHdrLines, "tile_size_rows"));
     762           2 :         if (CSLFetchNameValue(papszHdrLines, "tile_size_columns"))
     763           2 :             nTileXSize =
     764           2 :                 atoi(CSLFetchNameValue(papszHdrLines, "tile_size_columns"));
     765             : 
     766           2 :         if (nTileXSize <= 0 || nTileYSize <= 0 ||
     767           6 :             poDS->nRasterXSize - 1 > INT_MAX - nTileXSize ||
     768           2 :             poDS->nRasterYSize - 1 > INT_MAX - nTileYSize)
     769             :         {
     770           0 :             return nullptr;
     771             :         }
     772             :     }
     773             : 
     774             :     /* -------------------------------------------------------------------- */
     775             :     /*      Read the directory to find matching band files.                 */
     776             :     /* -------------------------------------------------------------------- */
     777             :     char *const pszTargetPath =
     778           7 :         CPLStrdup(CPLGetPathSafe(poOpenInfo->pszFilename).c_str());
     779             :     char *const pszTargetBase =
     780           7 :         CPLStrdup(CPLGetBasenameSafe(poOpenInfo->pszFilename).c_str());
     781             :     char **papszDirFiles =
     782           7 :         VSIReadDir(CPLGetPathSafe(poOpenInfo->pszFilename).c_str());
     783           7 :     if (papszDirFiles == nullptr)
     784             :     {
     785           0 :         CPLFree(pszTargetPath);
     786           0 :         CPLFree(pszTargetBase);
     787           0 :         return nullptr;
     788             :     }
     789             : 
     790           7 :     int nSkipped = 0;
     791           7 :     for (int nRawBand = 0; true; nRawBand++)
     792             :     {
     793             :         /* Find the next raw band file. */
     794             : 
     795          14 :         int i = 0;  // Used after for.
     796          72 :         for (; papszDirFiles[i] != nullptr; i++)
     797             :         {
     798          65 :             if (!EQUAL(CPLGetBasenameSafe(papszDirFiles[i]).c_str(),
     799             :                        pszTargetBase))
     800          41 :                 continue;
     801             : 
     802             :             const std::string osExtension =
     803          24 :                 CPLGetExtensionSafe(papszDirFiles[i]);
     804          24 :             if (osExtension.size() >= 2 &&
     805          24 :                 isdigit(static_cast<unsigned char>(osExtension[1])) &&
     806          55 :                 atoi(osExtension.c_str() + 1) == nRawBand &&
     807           7 :                 strchr("bBcCiIjJrRxXzZ", osExtension[0]) != nullptr)
     808           7 :                 break;
     809             :         }
     810             : 
     811          14 :         if (papszDirFiles[i] == nullptr)
     812           7 :             break;
     813             : 
     814             :         /* open the file for required level of access */
     815             :         const std::string osRawFilename =
     816           7 :             CPLFormFilenameSafe(pszTargetPath, papszDirFiles[i], nullptr);
     817             : 
     818           7 :         VSILFILE *fpRaw = nullptr;
     819           7 :         if (poOpenInfo->eAccess == GA_Update)
     820           0 :             fpRaw = VSIFOpenL(osRawFilename.c_str(), "rb+");
     821             :         else
     822           7 :             fpRaw = VSIFOpenL(osRawFilename.c_str(), "rb");
     823             : 
     824           7 :         if (fpRaw == nullptr)
     825             :         {
     826           0 :             CPLError(CE_Warning, CPLE_OpenFailed,
     827             :                      "Unable to open %s ... skipping.", osRawFilename.c_str());
     828           0 :             nSkipped++;
     829           0 :             continue;
     830             :         }
     831          14 :         poDS->m_papszFileList =
     832           7 :             CSLAddString(poDS->m_papszFileList, osRawFilename.c_str());
     833             : 
     834           7 :         GDALDataType eDataType = GDT_Unknown;
     835           7 :         const std::string osExt = CPLGetExtensionSafe(papszDirFiles[i]);
     836           7 :         if (pszRefinedType != nullptr)
     837             :         {
     838           0 :             if (EQUAL(pszRefinedType, "C*4"))
     839           0 :                 eDataType = GDT_CFloat32;
     840           0 :             else if (EQUAL(pszRefinedType, "C*8"))
     841           0 :                 eDataType = GDT_CFloat64;
     842           0 :             else if (EQUAL(pszRefinedType, "R*4"))
     843           0 :                 eDataType = GDT_Float32;
     844           0 :             else if (EQUAL(pszRefinedType, "R*8"))
     845           0 :                 eDataType = GDT_Float64;
     846           0 :             else if (EQUAL(pszRefinedType, "I*1"))
     847           0 :                 eDataType = GDT_Byte;
     848           0 :             else if (EQUAL(pszRefinedType, "I*2"))
     849           0 :                 eDataType = GDT_Int16;
     850           0 :             else if (EQUAL(pszRefinedType, "I*4"))
     851           0 :                 eDataType = GDT_Int32;
     852           0 :             else if (EQUAL(pszRefinedType, "U*2"))
     853           0 :                 eDataType = GDT_UInt16;
     854           0 :             else if (EQUAL(pszRefinedType, "U*4"))
     855           0 :                 eDataType = GDT_UInt32;
     856           0 :             else if (EQUAL(pszRefinedType, "J*1"))
     857             :             {
     858           0 :                 CPLError(
     859             :                     CE_Warning, CPLE_OpenFailed,
     860             :                     "Unable to open band %d because type J*1 is not handled. "
     861             :                     "Skipping.",
     862             :                     nRawBand + 1);
     863           0 :                 nSkipped++;
     864           0 :                 CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
     865           0 :                 continue;  // Does not support 1 byte complex.
     866             :             }
     867           0 :             else if (EQUAL(pszRefinedType, "J*2"))
     868           0 :                 eDataType = GDT_CInt16;
     869           0 :             else if (EQUAL(pszRefinedType, "K*4"))
     870           0 :                 eDataType = GDT_CInt32;
     871             :             else
     872             :             {
     873           0 :                 CPLError(
     874             :                     CE_Warning, CPLE_OpenFailed,
     875             :                     "Unable to open band %d because type %s is not handled. "
     876             :                     "Skipping.\n",
     877             :                     nRawBand + 1, pszRefinedType);
     878           0 :                 nSkipped++;
     879           0 :                 CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
     880           0 :                 continue;
     881             :             }
     882             :         }
     883           7 :         else if (STARTS_WITH_CI(osExt.c_str(), "b"))
     884             :         {
     885           6 :             eDataType = GDT_Byte;
     886             :         }
     887           1 :         else if (STARTS_WITH_CI(osExt.c_str(), "i"))
     888             :         {
     889           0 :             eDataType = GDT_UInt16;
     890             :         }
     891           1 :         else if (STARTS_WITH_CI(osExt.c_str(), "j"))
     892             :         {
     893           0 :             eDataType = GDT_CInt16;
     894             :         }
     895           1 :         else if (STARTS_WITH_CI(osExt.c_str(), "r"))
     896             :         {
     897           1 :             eDataType = GDT_Float32;
     898             :         }
     899           0 :         else if (STARTS_WITH_CI(osExt.c_str(), "x"))
     900             :         {
     901           0 :             eDataType = GDT_CFloat32;
     902             :         }
     903             :         else
     904             :         {
     905           0 :             CPLError(CE_Warning, CPLE_OpenFailed,
     906             :                      "Unable to open band %d because extension %s is not "
     907             :                      "handled.  Skipping.",
     908             :                      nRawBand + 1, osExt.c_str());
     909           0 :             nSkipped++;
     910           0 :             CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
     911           0 :             continue;
     912             :         }
     913             : 
     914           7 :         const int nBand = poDS->GetRasterCount() + 1;
     915             : 
     916           7 :         const int nPixelOffset = GDALGetDataTypeSizeBytes(eDataType);
     917           0 :         std::unique_ptr<GDALRasterBand> poBand;
     918             : 
     919           7 :         if (bTiled)
     920             :         {
     921           4 :             poBand = std::make_unique<MFFTiledBand>(poDS.get(), nBand, fpRaw,
     922             :                                                     nTileXSize, nTileYSize,
     923           2 :                                                     eDataType, eByteOrder);
     924             :         }
     925             :         else
     926             :         {
     927          10 :             if (nPixelOffset != 0 &&
     928           5 :                 poDS->GetRasterXSize() > INT_MAX / nPixelOffset)
     929             :             {
     930           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     931             :                          "Int overflow occurred... skipping");
     932           0 :                 nSkipped++;
     933           0 :                 CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
     934           0 :                 continue;
     935             :             }
     936             : 
     937          10 :             poBand = RawRasterBand::Create(
     938           5 :                 poDS.get(), nBand, fpRaw, 0, nPixelOffset,
     939           5 :                 nPixelOffset * poDS->GetRasterXSize(), eDataType, eByteOrder,
     940           5 :                 RawRasterBand::OwnFP::YES);
     941             :         }
     942             : 
     943           7 :         poDS->SetBand(nBand, std::move(poBand));
     944           7 :     }
     945             : 
     946           7 :     CPLFree(pszTargetPath);
     947           7 :     CPLFree(pszTargetBase);
     948           7 :     CSLDestroy(papszDirFiles);
     949             : 
     950             :     /* -------------------------------------------------------------------- */
     951             :     /*      Check if we have bands.                                         */
     952             :     /* -------------------------------------------------------------------- */
     953           7 :     if (poDS->GetRasterCount() == 0)
     954             :     {
     955           0 :         if (nSkipped > 0 && poOpenInfo->eAccess)
     956             :         {
     957           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
     958             :                      "Failed to open %d files that were apparently bands.  "
     959             :                      "Perhaps this dataset is readonly?",
     960             :                      nSkipped);
     961           0 :             return nullptr;
     962             :         }
     963             :         else
     964             :         {
     965           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
     966             :                      "MFF header file read successfully, but no bands "
     967             :                      "were successfully found and opened.");
     968           0 :             return nullptr;
     969             :         }
     970             :     }
     971             : 
     972             :     /* -------------------------------------------------------------------- */
     973             :     /*      Set all information from the .hdr that isn't well know to be    */
     974             :     /*      metadata.                                                       */
     975             :     /* -------------------------------------------------------------------- */
     976          81 :     for (int i = 0; papszHdrLines[i] != nullptr; i++)
     977             :     {
     978          74 :         char *pszName = nullptr;
     979             : 
     980          74 :         const char *pszValue = CPLParseNameValue(papszHdrLines[i], &pszName);
     981          74 :         if (pszName == nullptr || pszValue == nullptr)
     982           3 :             continue;
     983             : 
     984          71 :         if (!EQUAL(pszName, "END") && !EQUAL(pszName, "FILE_TYPE") &&
     985          68 :             !EQUAL(pszName, "BYTE_ORDER") && !EQUAL(pszName, "no_columns") &&
     986          65 :             !EQUAL(pszName, "no_rows") && !EQUAL(pszName, "type") &&
     987          63 :             !EQUAL(pszName, "tile_size_rows") &&
     988          61 :             !EQUAL(pszName, "tile_size_columns") &&
     989          59 :             !EQUAL(pszName, "IMAGE_FILE_FORMAT") &&
     990          52 :             !EQUAL(pszName, "IMAGE_LINES") && !EQUAL(pszName, "LINE_SAMPLES"))
     991             :         {
     992          38 :             poDS->SetMetadataItem(pszName, pszValue);
     993             :         }
     994             : 
     995          71 :         CPLFree(pszName);
     996             :     }
     997             : 
     998             :     /* -------------------------------------------------------------------- */
     999             :     /*      Any GCPs in header file?                                        */
    1000             :     /* -------------------------------------------------------------------- */
    1001           7 :     poDS->ScanForGCPs();
    1002           7 :     poDS->ScanForProjectionInfo();
    1003           7 :     if (poDS->nGCPCount == 0)
    1004           1 :         poDS->m_oGCPSRS.Clear();
    1005             : 
    1006             :     /* -------------------------------------------------------------------- */
    1007             :     /*      Initialize any PAM information.                                 */
    1008             :     /* -------------------------------------------------------------------- */
    1009           7 :     poDS->SetDescription(poOpenInfo->pszFilename);
    1010           7 :     poDS->TryLoadXML();
    1011             : 
    1012             :     /* -------------------------------------------------------------------- */
    1013             :     /*      Check for overviews.                                            */
    1014             :     /* -------------------------------------------------------------------- */
    1015           7 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
    1016             : 
    1017           7 :     return poDS.release();
    1018             : }
    1019             : 
    1020             : /************************************************************************/
    1021             : /*                         GDALRegister_MFF()                           */
    1022             : /************************************************************************/
    1023             : 
    1024        1667 : void GDALRegister_MFF()
    1025             : 
    1026             : {
    1027        1667 :     if (GDALGetDriverByName("MFF") != nullptr)
    1028         282 :         return;
    1029             : 
    1030        1385 :     GDALDriver *poDriver = new GDALDriver();
    1031             : 
    1032        1385 :     poDriver->SetDescription("MFF");
    1033        1385 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1034        1385 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Vexcel MFF Raster");
    1035        1385 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/mff.html");
    1036        1385 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "hdr");
    1037        1385 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1038             : 
    1039        1385 :     poDriver->pfnOpen = MFFDataset::Open;
    1040             : 
    1041        1385 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1042             : }

Generated by: LCOV version 1.14