LCOV - code coverage report
Current view: top level - frmts/raw - mffdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 332 451 73.6 %
Date: 2025-10-21 15:59:38 Functions: 14 19 73.7 %

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

Generated by: LCOV version 1.14