LCOV - code coverage report
Current view: top level - frmts/raw - mffdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 335 454 73.8 %
Date: 2025-07-08 21:33:46 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             :     GDALGeoTransform m_gt{};
      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(GDALGeoTransform &gt) const 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 = DIV_ROUND_UP(nRasterXSize, nBlockXSize);
     147           1 :     const int nWordSize = GDALGetDataTypeSizeBytes(eDataType);
     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           8 : MFFDataset::MFFDataset()
     232             :     : nGCPCount(0), pasGCPList(nullptr), m_papszFileList(nullptr),
     233           8 :       papszHdrLines(nullptr), pafpBandFiles(nullptr)
     234             : {
     235           8 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     236           8 :     m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     237           8 : }
     238             : 
     239             : /************************************************************************/
     240             : /*                            ~MFFDataset()                            */
     241             : /************************************************************************/
     242             : 
     243          16 : MFFDataset::~MFFDataset()
     244             : 
     245             : {
     246           8 :     MFFDataset::Close();
     247          16 : }
     248             : 
     249             : /************************************************************************/
     250             : /*                              Close()                                 */
     251             : /************************************************************************/
     252             : 
     253          15 : CPLErr MFFDataset::Close()
     254             : {
     255          15 :     CPLErr eErr = CE_None;
     256          15 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     257             :     {
     258           8 :         if (MFFDataset::FlushCache(true) != CE_None)
     259           0 :             eErr = CE_Failure;
     260             : 
     261           8 :         CSLDestroy(papszHdrLines);
     262           8 :         if (pafpBandFiles)
     263             :         {
     264           0 :             for (int i = 0; i < GetRasterCount(); i++)
     265             :             {
     266           0 :                 if (pafpBandFiles[i])
     267             :                 {
     268           0 :                     if (VSIFCloseL(pafpBandFiles[i]) != 0)
     269             :                     {
     270           0 :                         eErr = CE_Failure;
     271           0 :                         CPLError(CE_Failure, CPLE_FileIO, "I/O error");
     272             :                     }
     273             :                 }
     274             :             }
     275           0 :             CPLFree(pafpBandFiles);
     276             :         }
     277             : 
     278           8 :         if (nGCPCount > 0)
     279             :         {
     280           6 :             GDALDeinitGCPs(nGCPCount, pasGCPList);
     281             :         }
     282           8 :         CPLFree(pasGCPList);
     283           8 :         CSLDestroy(m_papszFileList);
     284             : 
     285           8 :         if (GDALPamDataset::Close() != CE_None)
     286           0 :             eErr = CE_Failure;
     287             :     }
     288          15 :     return eErr;
     289             : }
     290             : 
     291             : /************************************************************************/
     292             : /*                            GetFileList()                             */
     293             : /************************************************************************/
     294             : 
     295           3 : char **MFFDataset::GetFileList()
     296             : {
     297           3 :     char **papszFileList = RawDataset::GetFileList();
     298           3 :     papszFileList = CSLInsertStrings(papszFileList, -1, m_papszFileList);
     299           3 :     return papszFileList;
     300             : }
     301             : 
     302             : /************************************************************************/
     303             : /*                            GetGCPCount()                             */
     304             : /************************************************************************/
     305             : 
     306           0 : int MFFDataset::GetGCPCount()
     307             : 
     308             : {
     309           0 :     return nGCPCount;
     310             : }
     311             : 
     312             : /************************************************************************/
     313             : /*                          GetGeoTransform()                           */
     314             : /************************************************************************/
     315             : 
     316           0 : CPLErr MFFDataset::GetGeoTransform(GDALGeoTransform &gt) const
     317             : {
     318           0 :     gt = m_gt;
     319           0 :     return CE_None;
     320             : }
     321             : 
     322             : /************************************************************************/
     323             : /*                               GetGCP()                               */
     324             : /************************************************************************/
     325             : 
     326           0 : const GDAL_GCP *MFFDataset::GetGCPs()
     327             : 
     328             : {
     329           0 :     return pasGCPList;
     330             : }
     331             : 
     332             : /************************************************************************/
     333             : /*                            ScanForGCPs()                             */
     334             : /************************************************************************/
     335             : 
     336           7 : void MFFDataset::ScanForGCPs()
     337             : 
     338             : {
     339           7 :     int NUM_GCPS = 0;
     340             : 
     341           7 :     if (CSLFetchNameValue(papszHdrLines, "NUM_GCPS") != nullptr)
     342           4 :         NUM_GCPS = atoi(CSLFetchNameValue(papszHdrLines, "NUM_GCPS"));
     343           7 :     if (NUM_GCPS < 0)
     344           0 :         return;
     345             : 
     346           7 :     nGCPCount = 0;
     347           7 :     pasGCPList =
     348           7 :         static_cast<GDAL_GCP *>(VSICalloc(sizeof(GDAL_GCP), 5 + NUM_GCPS));
     349           7 :     if (pasGCPList == nullptr)
     350           0 :         return;
     351             : 
     352          42 :     for (int nCorner = 0; nCorner < 5; nCorner++)
     353             :     {
     354          35 :         const char *pszBase = nullptr;
     355          35 :         double dfRasterX = 0.0;
     356          35 :         double dfRasterY = 0.0;
     357             : 
     358          35 :         if (nCorner == 0)
     359             :         {
     360           7 :             dfRasterX = 0.5;
     361           7 :             dfRasterY = 0.5;
     362           7 :             pszBase = "TOP_LEFT_CORNER";
     363             :         }
     364          28 :         else if (nCorner == 1)
     365             :         {
     366           7 :             dfRasterX = GetRasterXSize() - 0.5;
     367           7 :             dfRasterY = 0.5;
     368           7 :             pszBase = "TOP_RIGHT_CORNER";
     369             :         }
     370          21 :         else if (nCorner == 2)
     371             :         {
     372           7 :             dfRasterX = GetRasterXSize() - 0.5;
     373           7 :             dfRasterY = GetRasterYSize() - 0.5;
     374           7 :             pszBase = "BOTTOM_RIGHT_CORNER";
     375             :         }
     376          14 :         else if (nCorner == 3)
     377             :         {
     378           7 :             dfRasterX = 0.5;
     379           7 :             dfRasterY = GetRasterYSize() - 0.5;
     380           7 :             pszBase = "BOTTOM_LEFT_CORNER";
     381             :         }
     382             :         else /* if( nCorner == 4 ) */
     383             :         {
     384           7 :             dfRasterX = GetRasterXSize() / 2.0;
     385           7 :             dfRasterY = GetRasterYSize() / 2.0;
     386           7 :             pszBase = "CENTRE";
     387             :         }
     388             : 
     389          35 :         char szLatName[40] = {'\0'};
     390          35 :         char szLongName[40] = {'\0'};
     391          35 :         snprintf(szLatName, sizeof(szLatName), "%s_LATITUDE", pszBase);
     392          35 :         snprintf(szLongName, sizeof(szLongName), "%s_LONGITUDE", pszBase);
     393             : 
     394          45 :         if (CSLFetchNameValue(papszHdrLines, szLatName) != nullptr &&
     395          10 :             CSLFetchNameValue(papszHdrLines, szLongName) != nullptr)
     396             :         {
     397          10 :             GDALInitGCPs(1, pasGCPList + nGCPCount);
     398             : 
     399          10 :             CPLFree(pasGCPList[nGCPCount].pszId);
     400             : 
     401          10 :             pasGCPList[nGCPCount].pszId = CPLStrdup(pszBase);
     402             : 
     403          20 :             pasGCPList[nGCPCount].dfGCPX =
     404          10 :                 CPLAtof(CSLFetchNameValue(papszHdrLines, szLongName));
     405          20 :             pasGCPList[nGCPCount].dfGCPY =
     406          10 :                 CPLAtof(CSLFetchNameValue(papszHdrLines, szLatName));
     407          10 :             pasGCPList[nGCPCount].dfGCPZ = 0.0;
     408             : 
     409          10 :             pasGCPList[nGCPCount].dfGCPPixel = dfRasterX;
     410          10 :             pasGCPList[nGCPCount].dfGCPLine = dfRasterY;
     411             : 
     412          10 :             nGCPCount++;
     413             :         }
     414             :     }
     415             : 
     416             :     /* -------------------------------------------------------------------- */
     417             :     /*      Collect standalone GCPs.  They look like:                       */
     418             :     /*                                                                      */
     419             :     /*      GCPn = row, col, lat, long                                      */
     420             :     /*      GCP1 = 1, 1, 45.0, -75.0                                        */
     421             :     /* -------------------------------------------------------------------- */
     422          11 :     for (int i = 0; i < NUM_GCPS; i++)
     423             :     {
     424           4 :         char szName[25] = {'\0'};
     425           4 :         snprintf(szName, sizeof(szName), "GCP%d", i + 1);
     426           4 :         if (CSLFetchNameValue(papszHdrLines, szName) == nullptr)
     427           0 :             continue;
     428             : 
     429           4 :         char **papszTokens = CSLTokenizeStringComplex(
     430           4 :             CSLFetchNameValue(papszHdrLines, szName), ",", FALSE, FALSE);
     431           4 :         if (CSLCount(papszTokens) == 4)
     432             :         {
     433           4 :             GDALInitGCPs(1, pasGCPList + nGCPCount);
     434             : 
     435           4 :             CPLFree(pasGCPList[nGCPCount].pszId);
     436           4 :             pasGCPList[nGCPCount].pszId = CPLStrdup(szName);
     437             : 
     438           4 :             pasGCPList[nGCPCount].dfGCPX = CPLAtof(papszTokens[3]);
     439           4 :             pasGCPList[nGCPCount].dfGCPY = CPLAtof(papszTokens[2]);
     440           4 :             pasGCPList[nGCPCount].dfGCPZ = 0.0;
     441           4 :             pasGCPList[nGCPCount].dfGCPPixel = CPLAtof(papszTokens[1]) + 0.5;
     442           4 :             pasGCPList[nGCPCount].dfGCPLine = CPLAtof(papszTokens[0]) + 0.5;
     443             : 
     444           4 :             nGCPCount++;
     445             :         }
     446             : 
     447           4 :         CSLDestroy(papszTokens);
     448             :     }
     449             : }
     450             : 
     451             : /************************************************************************/
     452             : /*                        ScanForProjectionInfo                         */
     453             : /************************************************************************/
     454             : 
     455           7 : void MFFDataset::ScanForProjectionInfo()
     456             : {
     457             :     const char *pszProjName =
     458           7 :         CSLFetchNameValue(papszHdrLines, "PROJECTION_NAME");
     459             :     const char *pszOriginLong =
     460           7 :         CSLFetchNameValue(papszHdrLines, "PROJECTION_ORIGIN_LONGITUDE");
     461             :     const char *pszSpheroidName =
     462           7 :         CSLFetchNameValue(papszHdrLines, "SPHEROID_NAME");
     463             : 
     464           7 :     if (pszProjName == nullptr)
     465             :     {
     466           1 :         m_oSRS.Clear();
     467           1 :         m_oGCPSRS.Clear();
     468           1 :         return;
     469             :     }
     470           6 :     else if ((!EQUAL(pszProjName, "utm")) && (!EQUAL(pszProjName, "ll")))
     471             :     {
     472           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     473             :                  "Only utm and lat/long projections are currently supported.");
     474           0 :         m_oSRS.Clear();
     475           0 :         m_oGCPSRS.Clear();
     476           0 :         return;
     477             :     }
     478           6 :     MFFSpheroidList *mffEllipsoids = new MFFSpheroidList;
     479             : 
     480          12 :     OGRSpatialReference oProj;
     481           6 :     oProj.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     482           6 :     if (EQUAL(pszProjName, "utm"))
     483             :     {
     484             :         int nZone;
     485             : 
     486           6 :         if (pszOriginLong == nullptr)
     487             :         {
     488             :             // If origin not specified, assume 0.0.
     489           4 :             CPLError(
     490             :                 CE_Warning, CPLE_AppDefined,
     491             :                 "No projection origin longitude specified.  Assuming 0.0.");
     492           4 :             nZone = 31;
     493             :         }
     494             :         else
     495           2 :             nZone = 31 + static_cast<int>(floor(CPLAtof(pszOriginLong) / 6.0));
     496             : 
     497           6 :         if (nGCPCount >= 5 && pasGCPList[4].dfGCPY < 0)
     498           0 :             oProj.SetUTM(nZone, 0);
     499             :         else
     500           6 :             oProj.SetUTM(nZone, 1);
     501             : 
     502           6 :         if (pszOriginLong != nullptr)
     503           2 :             oProj.SetProjParm(SRS_PP_CENTRAL_MERIDIAN, CPLAtof(pszOriginLong));
     504             :     }
     505             : 
     506          12 :     OGRSpatialReference oLL;
     507           6 :     oLL.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     508           6 :     if (pszOriginLong != nullptr)
     509           2 :         oLL.SetProjParm(SRS_PP_LONGITUDE_OF_ORIGIN, CPLAtof(pszOriginLong));
     510             : 
     511           6 :     if (pszSpheroidName == nullptr)
     512             :     {
     513           4 :         CPLError(CE_Warning, CPLE_AppDefined,
     514             :                  "Unspecified ellipsoid.  Using wgs-84 parameters.\n");
     515             : 
     516           4 :         oProj.SetWellKnownGeogCS("WGS84");
     517           4 :         oLL.SetWellKnownGeogCS("WGS84");
     518             :     }
     519             :     else
     520             :     {
     521           2 :         if (mffEllipsoids->SpheroidInList(pszSpheroidName))
     522             :         {
     523           2 :             oProj.SetGeogCS(
     524             :                 "unknown", "unknown", pszSpheroidName,
     525             :                 mffEllipsoids->GetSpheroidEqRadius(pszSpheroidName),
     526             :                 mffEllipsoids->GetSpheroidInverseFlattening(pszSpheroidName));
     527           2 :             oLL.SetGeogCS(
     528             :                 "unknown", "unknown", pszSpheroidName,
     529             :                 mffEllipsoids->GetSpheroidEqRadius(pszSpheroidName),
     530             :                 mffEllipsoids->GetSpheroidInverseFlattening(pszSpheroidName));
     531             :         }
     532           0 :         else if (EQUAL(pszSpheroidName, "USER_DEFINED"))
     533             :         {
     534             :             const char *pszSpheroidEqRadius =
     535           0 :                 CSLFetchNameValue(papszHdrLines, "SPHEROID_EQUATORIAL_RADIUS");
     536             :             const char *pszSpheroidPolarRadius =
     537           0 :                 CSLFetchNameValue(papszHdrLines, "SPHEROID_POLAR_RADIUS");
     538           0 :             if ((pszSpheroidEqRadius != nullptr) &&
     539             :                 (pszSpheroidPolarRadius != nullptr))
     540             :             {
     541           0 :                 const double eq_radius = CPLAtof(pszSpheroidEqRadius);
     542           0 :                 const double polar_radius = CPLAtof(pszSpheroidPolarRadius);
     543           0 :                 oProj.SetGeogCS("unknown", "unknown", "unknown", eq_radius,
     544           0 :                                 eq_radius / (eq_radius - polar_radius));
     545           0 :                 oLL.SetGeogCS("unknown", "unknown", "unknown", eq_radius,
     546           0 :                               eq_radius / (eq_radius - polar_radius));
     547             :             }
     548             :             else
     549             :             {
     550           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     551             :                          "Radii not specified for user-defined ellipsoid. "
     552             :                          "Using wgs-84 parameters.");
     553           0 :                 oProj.SetWellKnownGeogCS("WGS84");
     554           0 :                 oLL.SetWellKnownGeogCS("WGS84");
     555             :             }
     556             :         }
     557             :         else
     558             :         {
     559           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     560             :                      "Unrecognized ellipsoid.  Using wgs-84 parameters.");
     561           0 :             oProj.SetWellKnownGeogCS("WGS84");
     562           0 :             oLL.SetWellKnownGeogCS("WGS84");
     563             :         }
     564             :     }
     565             : 
     566             :     /* If a geotransform is sufficient to represent the GCP's (i.e. each */
     567             :     /* estimated gcp is within 0.25*pixel size of the actual value- this */
     568             :     /* is the test applied by GDALGCPsToGeoTransform), store the         */
     569             :     /* geotransform.                                                     */
     570           6 :     bool transform_ok = false;
     571             : 
     572           6 :     if (EQUAL(pszProjName, "LL"))
     573             :     {
     574           0 :         transform_ok = CPL_TO_BOOL(
     575           0 :             GDALGCPsToGeoTransform(nGCPCount, pasGCPList, m_gt.data(), 0));
     576             :     }
     577             :     else
     578             :     {
     579             :         OGRCoordinateTransformation *poTransform =
     580           6 :             OGRCreateCoordinateTransformation(&oLL, &oProj);
     581           6 :         bool bSuccess = true;
     582           6 :         if (poTransform == nullptr)
     583             :         {
     584           0 :             CPLErrorReset();
     585           0 :             bSuccess = FALSE;
     586             :         }
     587             : 
     588             :         double *dfPrjX =
     589           6 :             static_cast<double *>(CPLMalloc(nGCPCount * sizeof(double)));
     590             :         double *dfPrjY =
     591           6 :             static_cast<double *>(CPLMalloc(nGCPCount * sizeof(double)));
     592             : 
     593          20 :         for (int gcp_index = 0; gcp_index < nGCPCount; gcp_index++)
     594             :         {
     595          14 :             dfPrjX[gcp_index] = pasGCPList[gcp_index].dfGCPX;
     596          14 :             dfPrjY[gcp_index] = pasGCPList[gcp_index].dfGCPY;
     597             : 
     598          28 :             if (bSuccess && !poTransform->Transform(1, &(dfPrjX[gcp_index]),
     599          14 :                                                     &(dfPrjY[gcp_index])))
     600           0 :                 bSuccess = FALSE;
     601             :         }
     602             : 
     603           6 :         if (bSuccess)
     604             :         {
     605          20 :             for (int gcp_index = 0; gcp_index < nGCPCount; gcp_index++)
     606             :             {
     607          14 :                 pasGCPList[gcp_index].dfGCPX = dfPrjX[gcp_index];
     608          14 :                 pasGCPList[gcp_index].dfGCPY = dfPrjY[gcp_index];
     609             :             }
     610          12 :             transform_ok = CPL_TO_BOOL(
     611           6 :                 GDALGCPsToGeoTransform(nGCPCount, pasGCPList, m_gt.data(), 0));
     612             :         }
     613             : 
     614           6 :         if (poTransform)
     615           6 :             delete poTransform;
     616             : 
     617           6 :         CPLFree(dfPrjX);
     618           6 :         CPLFree(dfPrjY);
     619             :     }
     620             : 
     621           6 :     m_oSRS = oProj;
     622           6 :     m_oGCPSRS = std::move(oProj);
     623             : 
     624           6 :     if (!transform_ok)
     625             :     {
     626             :         /* transform is sufficient in some cases (slant range, standalone gcps)
     627             :          */
     628           4 :         m_gt = GDALGeoTransform();
     629           4 :         m_oSRS.Clear();
     630             :     }
     631             : 
     632           6 :     delete mffEllipsoids;
     633             : }
     634             : 
     635             : /************************************************************************/
     636             : /*                                Open()                                */
     637             : /************************************************************************/
     638             : 
     639       33545 : GDALDataset *MFFDataset::Open(GDALOpenInfo *poOpenInfo)
     640             : 
     641             : {
     642             :     /* -------------------------------------------------------------------- */
     643             :     /*      We assume the user is pointing to the header file.              */
     644             :     /* -------------------------------------------------------------------- */
     645       33545 :     if (poOpenInfo->nHeaderBytes < 17 || poOpenInfo->fpL == nullptr)
     646       30246 :         return nullptr;
     647             : 
     648        3299 :     if (!poOpenInfo->IsExtensionEqualToCI("hdr"))
     649        3281 :         return nullptr;
     650             : 
     651             :     /* -------------------------------------------------------------------- */
     652             :     /*      Load the .hdr file, and compress white space out around the     */
     653             :     /*      equal sign.                                                     */
     654             :     /* -------------------------------------------------------------------- */
     655          18 :     char **papszHdrLines = CSLLoad(poOpenInfo->pszFilename);
     656          18 :     if (papszHdrLines == nullptr)
     657           0 :         return nullptr;
     658             : 
     659             :     // Remove spaces.  e.g.
     660             :     // SPHEROID_NAME = CLARKE_1866 -> SPHEROID_NAME=CLARKE_1866
     661         577 :     for (int i = 0; papszHdrLines[i] != nullptr; i++)
     662             :     {
     663         559 :         int iDst = 0;
     664         559 :         char *pszLine = papszHdrLines[i];
     665             : 
     666       16291 :         for (int iSrc = 0; pszLine[iSrc] != '\0'; iSrc++)
     667             :         {
     668       15732 :             if (pszLine[iSrc] != ' ')
     669             :             {
     670       13778 :                 pszLine[iDst++] = pszLine[iSrc];
     671             :             }
     672             :         }
     673         559 :         pszLine[iDst] = '\0';
     674             :     }
     675             : 
     676             :     /* -------------------------------------------------------------------- */
     677             :     /*      Verify it is an MFF file.                                       */
     678             :     /* -------------------------------------------------------------------- */
     679          26 :     if (CSLFetchNameValue(papszHdrLines, "IMAGE_FILE_FORMAT") != nullptr &&
     680           8 :         !EQUAL(CSLFetchNameValue(papszHdrLines, "IMAGE_FILE_FORMAT"), "MFF"))
     681             :     {
     682           0 :         CSLDestroy(papszHdrLines);
     683           0 :         return nullptr;
     684             :     }
     685             : 
     686          18 :     if ((CSLFetchNameValue(papszHdrLines, "IMAGE_LINES") == nullptr ||
     687          28 :          CSLFetchNameValue(papszHdrLines, "LINE_SAMPLES") == nullptr) &&
     688          10 :         (CSLFetchNameValue(papszHdrLines, "no_rows") == nullptr ||
     689           0 :          CSLFetchNameValue(papszHdrLines, "no_columns") == nullptr))
     690             :     {
     691          10 :         CSLDestroy(papszHdrLines);
     692          10 :         return nullptr;
     693             :     }
     694             : 
     695             :     /* -------------------------------------------------------------------- */
     696             :     /*      Create a corresponding GDALDataset.                             */
     697             :     /* -------------------------------------------------------------------- */
     698          16 :     auto poDS = std::make_unique<MFFDataset>();
     699             : 
     700           8 :     poDS->papszHdrLines = papszHdrLines;
     701             : 
     702           8 :     poDS->eAccess = poOpenInfo->eAccess;
     703             : 
     704             :     /* -------------------------------------------------------------------- */
     705             :     /*      Set some dataset wide information.                              */
     706             :     /* -------------------------------------------------------------------- */
     707          11 :     if (CSLFetchNameValue(papszHdrLines, "no_rows") != nullptr &&
     708           3 :         CSLFetchNameValue(papszHdrLines, "no_columns") != nullptr)
     709             :     {
     710           0 :         poDS->nRasterXSize =
     711           0 :             atoi(CSLFetchNameValue(papszHdrLines, "no_columns"));
     712           0 :         poDS->nRasterYSize = atoi(CSLFetchNameValue(papszHdrLines, "no_rows"));
     713             :     }
     714             :     else
     715             :     {
     716          16 :         poDS->nRasterXSize =
     717           8 :             atoi(CSLFetchNameValue(papszHdrLines, "LINE_SAMPLES"));
     718           8 :         poDS->nRasterYSize =
     719           8 :             atoi(CSLFetchNameValue(papszHdrLines, "IMAGE_LINES"));
     720             :     }
     721             : 
     722           8 :     if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
     723             :     {
     724           0 :         return nullptr;
     725             :     }
     726             : 
     727           8 :     RawRasterBand::ByteOrder eByteOrder = RawRasterBand::NATIVE_BYTE_ORDER;
     728             : 
     729           8 :     const char *pszByteOrder = CSLFetchNameValue(papszHdrLines, "BYTE_ORDER");
     730           8 :     if (pszByteOrder)
     731             :     {
     732           3 :         eByteOrder = EQUAL(pszByteOrder, "LSB")
     733           3 :                          ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
     734             :                          : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN;
     735             :     }
     736             : 
     737             :     /* -------------------------------------------------------------------- */
     738             :     /*      Get some information specific to APP tiled files.               */
     739             :     /* -------------------------------------------------------------------- */
     740           8 :     int nTileXSize = 0;
     741           8 :     int nTileYSize = 0;
     742           8 :     const char *pszRefinedType = CSLFetchNameValue(papszHdrLines, "type");
     743           8 :     const bool bTiled = CSLFetchNameValue(papszHdrLines, "no_rows") != nullptr;
     744             : 
     745           8 :     if (bTiled)
     746             :     {
     747           3 :         if (CSLFetchNameValue(papszHdrLines, "tile_size_rows"))
     748           3 :             nTileYSize =
     749           3 :                 atoi(CSLFetchNameValue(papszHdrLines, "tile_size_rows"));
     750           3 :         if (CSLFetchNameValue(papszHdrLines, "tile_size_columns"))
     751           3 :             nTileXSize =
     752           3 :                 atoi(CSLFetchNameValue(papszHdrLines, "tile_size_columns"));
     753             : 
     754           3 :         if (nTileXSize <= 0 || nTileYSize <= 0 ||
     755           9 :             poDS->nRasterXSize - 1 > INT_MAX - nTileXSize ||
     756           3 :             poDS->nRasterYSize - 1 > INT_MAX - nTileYSize)
     757             :         {
     758           0 :             return nullptr;
     759             :         }
     760             :     }
     761             : 
     762             :     /* -------------------------------------------------------------------- */
     763             :     /*      Read the directory to find matching band files.                 */
     764             :     /* -------------------------------------------------------------------- */
     765          16 :     const std::string osTargetPath = CPLGetPathSafe(poOpenInfo->pszFilename);
     766             :     const std::string osTargetBase =
     767          16 :         CPLGetBasenameSafe(poOpenInfo->pszFilename);
     768             :     const CPLStringList aosDirFiles(
     769          16 :         VSIReadDir(CPLGetPathSafe(poOpenInfo->pszFilename).c_str()));
     770           8 :     if (aosDirFiles.empty())
     771             :     {
     772           0 :         return nullptr;
     773             :     }
     774             : 
     775           8 :     int nSkipped = 0;
     776           8 :     for (int nRawBand = 0; true; nRawBand++)
     777             :     {
     778             :         /* Find the next raw band file. */
     779             : 
     780          15 :         int i = 0;  // Used after for.
     781          76 :         for (; i < aosDirFiles.size(); i++)
     782             :         {
     783          69 :             if (!EQUAL(CPLGetBasenameSafe(aosDirFiles[i]).c_str(),
     784             :                        osTargetBase.c_str()))
     785          46 :                 continue;
     786             : 
     787          23 :             const std::string osExtension = CPLGetExtensionSafe(aosDirFiles[i]);
     788          23 :             if (osExtension.size() >= 2 &&
     789          23 :                 isdigit(static_cast<unsigned char>(osExtension[1])) &&
     790          54 :                 atoi(osExtension.c_str() + 1) == nRawBand &&
     791           8 :                 strchr("bBcCiIjJrRxXzZ", osExtension[0]) != nullptr)
     792           8 :                 break;
     793             :         }
     794             : 
     795          15 :         if (i == aosDirFiles.size())
     796           7 :             break;
     797             : 
     798             :         /* open the file for required level of access */
     799             :         const std::string osRawFilename =
     800           8 :             CPLFormFilenameSafe(osTargetPath.c_str(), aosDirFiles[i], nullptr);
     801             : 
     802           8 :         VSILFILE *fpRaw = nullptr;
     803           8 :         if (poOpenInfo->eAccess == GA_Update)
     804           0 :             fpRaw = VSIFOpenL(osRawFilename.c_str(), "rb+");
     805             :         else
     806           8 :             fpRaw = VSIFOpenL(osRawFilename.c_str(), "rb");
     807             : 
     808           8 :         if (fpRaw == nullptr)
     809             :         {
     810           0 :             CPLError(CE_Warning, CPLE_OpenFailed,
     811             :                      "Unable to open %s ... skipping.", osRawFilename.c_str());
     812           0 :             nSkipped++;
     813           0 :             continue;
     814             :         }
     815          16 :         poDS->m_papszFileList =
     816           8 :             CSLAddString(poDS->m_papszFileList, osRawFilename.c_str());
     817             : 
     818           8 :         GDALDataType eDataType = GDT_Unknown;
     819           8 :         const std::string osExt = CPLGetExtensionSafe(aosDirFiles[i]);
     820           8 :         if (pszRefinedType != nullptr)
     821             :         {
     822           0 :             if (EQUAL(pszRefinedType, "C*4"))
     823           0 :                 eDataType = GDT_CFloat32;
     824           0 :             else if (EQUAL(pszRefinedType, "C*8"))
     825           0 :                 eDataType = GDT_CFloat64;
     826           0 :             else if (EQUAL(pszRefinedType, "R*4"))
     827           0 :                 eDataType = GDT_Float32;
     828           0 :             else if (EQUAL(pszRefinedType, "R*8"))
     829           0 :                 eDataType = GDT_Float64;
     830           0 :             else if (EQUAL(pszRefinedType, "I*1"))
     831           0 :                 eDataType = GDT_Byte;
     832           0 :             else if (EQUAL(pszRefinedType, "I*2"))
     833           0 :                 eDataType = GDT_Int16;
     834           0 :             else if (EQUAL(pszRefinedType, "I*4"))
     835           0 :                 eDataType = GDT_Int32;
     836           0 :             else if (EQUAL(pszRefinedType, "U*2"))
     837           0 :                 eDataType = GDT_UInt16;
     838           0 :             else if (EQUAL(pszRefinedType, "U*4"))
     839           0 :                 eDataType = GDT_UInt32;
     840           0 :             else if (EQUAL(pszRefinedType, "J*1"))
     841             :             {
     842           0 :                 CPLError(
     843             :                     CE_Warning, CPLE_OpenFailed,
     844             :                     "Unable to open band %d because type J*1 is not handled. "
     845             :                     "Skipping.",
     846             :                     nRawBand + 1);
     847           0 :                 nSkipped++;
     848           0 :                 CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
     849           0 :                 continue;  // Does not support 1 byte complex.
     850             :             }
     851           0 :             else if (EQUAL(pszRefinedType, "J*2"))
     852           0 :                 eDataType = GDT_CInt16;
     853           0 :             else if (EQUAL(pszRefinedType, "K*4"))
     854           0 :                 eDataType = GDT_CInt32;
     855             :             else
     856             :             {
     857           0 :                 CPLError(
     858             :                     CE_Warning, CPLE_OpenFailed,
     859             :                     "Unable to open band %d because type %s is not handled. "
     860             :                     "Skipping.\n",
     861             :                     nRawBand + 1, pszRefinedType);
     862           0 :                 nSkipped++;
     863           0 :                 CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
     864           0 :                 continue;
     865             :             }
     866             :         }
     867           8 :         else if (STARTS_WITH_CI(osExt.c_str(), "b"))
     868             :         {
     869           7 :             eDataType = GDT_Byte;
     870             :         }
     871           1 :         else if (STARTS_WITH_CI(osExt.c_str(), "i"))
     872             :         {
     873           0 :             eDataType = GDT_UInt16;
     874             :         }
     875           1 :         else if (STARTS_WITH_CI(osExt.c_str(), "j"))
     876             :         {
     877           0 :             eDataType = GDT_CInt16;
     878             :         }
     879           1 :         else if (STARTS_WITH_CI(osExt.c_str(), "r"))
     880             :         {
     881           1 :             eDataType = GDT_Float32;
     882             :         }
     883           0 :         else if (STARTS_WITH_CI(osExt.c_str(), "x"))
     884             :         {
     885           0 :             eDataType = GDT_CFloat32;
     886             :         }
     887             :         else
     888             :         {
     889           0 :             CPLError(CE_Warning, CPLE_OpenFailed,
     890             :                      "Unable to open band %d because extension %s is not "
     891             :                      "handled.  Skipping.",
     892             :                      nRawBand + 1, osExt.c_str());
     893           0 :             nSkipped++;
     894           0 :             CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
     895           0 :             continue;
     896             :         }
     897             : 
     898           8 :         const int nBand = poDS->GetRasterCount() + 1;
     899             : 
     900           8 :         const int nPixelOffset = GDALGetDataTypeSizeBytes(eDataType);
     901           0 :         std::unique_ptr<GDALRasterBand> poBand;
     902             : 
     903           8 :         if (bTiled)
     904             :         {
     905           3 :             if (nTileXSize > INT_MAX / nTileYSize / nPixelOffset)
     906             :             {
     907           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Too large tile");
     908           1 :                 CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
     909           1 :                 return nullptr;
     910             :             }
     911             : 
     912           4 :             poBand = std::make_unique<MFFTiledBand>(poDS.get(), nBand, fpRaw,
     913             :                                                     nTileXSize, nTileYSize,
     914           2 :                                                     eDataType, eByteOrder);
     915             :         }
     916             :         else
     917             :         {
     918          10 :             if (nPixelOffset != 0 &&
     919           5 :                 poDS->GetRasterXSize() > INT_MAX / nPixelOffset)
     920             :             {
     921           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     922             :                          "Int overflow occurred... skipping");
     923           0 :                 nSkipped++;
     924           0 :                 CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
     925           0 :                 continue;
     926             :             }
     927             : 
     928          10 :             poBand = RawRasterBand::Create(
     929           5 :                 poDS.get(), nBand, fpRaw, 0, nPixelOffset,
     930           5 :                 nPixelOffset * poDS->GetRasterXSize(), eDataType, eByteOrder,
     931           5 :                 RawRasterBand::OwnFP::YES);
     932             :         }
     933             : 
     934           7 :         poDS->SetBand(nBand, std::move(poBand));
     935           7 :     }
     936             : 
     937             :     /* -------------------------------------------------------------------- */
     938             :     /*      Check if we have bands.                                         */
     939             :     /* -------------------------------------------------------------------- */
     940           7 :     if (poDS->GetRasterCount() == 0)
     941             :     {
     942           0 :         if (nSkipped > 0 && poOpenInfo->eAccess)
     943             :         {
     944           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
     945             :                      "Failed to open %d files that were apparently bands.  "
     946             :                      "Perhaps this dataset is readonly?",
     947             :                      nSkipped);
     948           0 :             return nullptr;
     949             :         }
     950             :         else
     951             :         {
     952           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
     953             :                      "MFF header file read successfully, but no bands "
     954             :                      "were successfully found and opened.");
     955           0 :             return nullptr;
     956             :         }
     957             :     }
     958             : 
     959             :     /* -------------------------------------------------------------------- */
     960             :     /*      Set all information from the .hdr that isn't well know to be    */
     961             :     /*      metadata.                                                       */
     962             :     /* -------------------------------------------------------------------- */
     963          81 :     for (int i = 0; papszHdrLines[i] != nullptr; i++)
     964             :     {
     965          74 :         char *pszName = nullptr;
     966             : 
     967          74 :         const char *pszValue = CPLParseNameValue(papszHdrLines[i], &pszName);
     968          74 :         if (pszName == nullptr || pszValue == nullptr)
     969           3 :             continue;
     970             : 
     971          71 :         if (!EQUAL(pszName, "END") && !EQUAL(pszName, "FILE_TYPE") &&
     972          68 :             !EQUAL(pszName, "BYTE_ORDER") && !EQUAL(pszName, "no_columns") &&
     973          65 :             !EQUAL(pszName, "no_rows") && !EQUAL(pszName, "type") &&
     974          63 :             !EQUAL(pszName, "tile_size_rows") &&
     975          61 :             !EQUAL(pszName, "tile_size_columns") &&
     976          59 :             !EQUAL(pszName, "IMAGE_FILE_FORMAT") &&
     977          52 :             !EQUAL(pszName, "IMAGE_LINES") && !EQUAL(pszName, "LINE_SAMPLES"))
     978             :         {
     979          38 :             poDS->SetMetadataItem(pszName, pszValue);
     980             :         }
     981             : 
     982          71 :         CPLFree(pszName);
     983             :     }
     984             : 
     985             :     /* -------------------------------------------------------------------- */
     986             :     /*      Any GCPs in header file?                                        */
     987             :     /* -------------------------------------------------------------------- */
     988           7 :     poDS->ScanForGCPs();
     989           7 :     poDS->ScanForProjectionInfo();
     990           7 :     if (poDS->nGCPCount == 0)
     991           1 :         poDS->m_oGCPSRS.Clear();
     992             : 
     993             :     /* -------------------------------------------------------------------- */
     994             :     /*      Initialize any PAM information.                                 */
     995             :     /* -------------------------------------------------------------------- */
     996           7 :     poDS->SetDescription(poOpenInfo->pszFilename);
     997           7 :     poDS->TryLoadXML();
     998             : 
     999             :     /* -------------------------------------------------------------------- */
    1000             :     /*      Check for overviews.                                            */
    1001             :     /* -------------------------------------------------------------------- */
    1002           7 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
    1003             : 
    1004           7 :     return poDS.release();
    1005             : }
    1006             : 
    1007             : /************************************************************************/
    1008             : /*                         GDALRegister_MFF()                           */
    1009             : /************************************************************************/
    1010             : 
    1011        1935 : void GDALRegister_MFF()
    1012             : 
    1013             : {
    1014        1935 :     if (GDALGetDriverByName("MFF") != nullptr)
    1015         282 :         return;
    1016             : 
    1017        1653 :     GDALDriver *poDriver = new GDALDriver();
    1018             : 
    1019        1653 :     poDriver->SetDescription("MFF");
    1020        1653 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1021        1653 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Vexcel MFF Raster");
    1022        1653 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/mff.html");
    1023        1653 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "hdr");
    1024        1653 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1025             : 
    1026        1653 :     poDriver->pfnOpen = MFFDataset::Open;
    1027             : 
    1028        1653 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1029             : }

Generated by: LCOV version 1.14