LCOV - code coverage report
Current view: top level - frmts/leveller - levellerdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 174 511 34.1 %
Date: 2025-11-22 03:30:40 Functions: 20 56 35.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  * levellerdataset.cpp,v 1.22
       3             :  *
       4             :  * Project:  Leveller TER Driver
       5             :  * Purpose:  Reader for Leveller TER documents
       6             :  * Author:   Ray Gardener, Daylon Graphics Ltd.
       7             :  *
       8             :  * Portions of this module derived from GDAL drivers by
       9             :  * Frank Warmerdam, see http://www.gdal.org
      10             :  *
      11             :  ******************************************************************************
      12             :  * Copyright (c) 2005-2007 Daylon Graphics Ltd.
      13             :  * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
      14             :  *
      15             :  * SPDX-License-Identifier: MIT
      16             :  ****************************************************************************/
      17             : 
      18             : #include "gdal_frmts.h"
      19             : #include "gdal_pam.h"
      20             : #include "gdal_driver.h"
      21             : #include "gdal_drivermanager.h"
      22             : #include "gdal_openinfo.h"
      23             : #include "gdal_cpp_functions.h"
      24             : #include "ogr_spatialref.h"
      25             : 
      26        1540 : static bool str_equal(const char *_s1, const char *_s2)
      27             : {
      28        1540 :     return 0 == strcmp(_s1, _s2);
      29             : }
      30             : 
      31             : /*GDALDataset *LevellerCreateCopy( const char *, GDALDataset *, int, char **,
      32             :                                 GDALProgressFunc pfnProgress,
      33             :                                 void * pProgressData );
      34             : */
      35             : 
      36             : /************************************************************************/
      37             : /* ==================================================================== */
      38             : /*                              LevellerDataset                         */
      39             : /* ==================================================================== */
      40             : /************************************************************************/
      41             : 
      42             : enum
      43             : {
      44             :     // Leveller coordsys types.
      45             :     LEV_COORDSYS_RASTER = 0,
      46             :     LEV_COORDSYS_LOCAL,
      47             :     LEV_COORDSYS_GEO
      48             : };
      49             : 
      50             : enum
      51             : {
      52             :     // Leveller digital axis extent styles.
      53             :     LEV_DA_POSITIONED = 0,
      54             :     LEV_DA_SIZED,
      55             :     LEV_DA_PIXEL_SIZED
      56             : };
      57             : 
      58             : typedef enum
      59             : {
      60             :     // Measurement unit IDs, OEM version.
      61             :     UNITLABEL_UNKNOWN = 0x00000000,
      62             :     UNITLABEL_PIXEL = 0x70780000,
      63             :     UNITLABEL_PERCENT = 0x25000000,
      64             : 
      65             :     UNITLABEL_RADIAN = 0x72616400,
      66             :     UNITLABEL_DEGREE = 0x64656700,
      67             :     UNITLABEL_ARCMINUTE = 0x6172636D,
      68             :     UNITLABEL_ARCSECOND = 0x61726373,
      69             : 
      70             :     UNITLABEL_YM = 0x796D0000,
      71             :     UNITLABEL_ZM = 0x7A6D0000,
      72             :     UNITLABEL_AM = 0x616D0000,
      73             :     UNITLABEL_FM = 0x666D0000,
      74             :     UNITLABEL_PM = 0x706D0000,
      75             :     UNITLABEL_A = 0x41000000,
      76             :     UNITLABEL_NM = 0x6E6D0000,
      77             :     UNITLABEL_U = 0x75000000,
      78             :     UNITLABEL_UM = 0x756D0000,
      79             :     UNITLABEL_PPT = 0x70707400,
      80             :     UNITLABEL_PT = 0x70740000,
      81             :     UNITLABEL_MM = 0x6D6D0000,
      82             :     UNITLABEL_P = 0x70000000,
      83             :     UNITLABEL_CM = 0x636D0000,
      84             :     UNITLABEL_IN = 0x696E0000,
      85             :     UNITLABEL_DFT = 0x64667400,
      86             :     UNITLABEL_DM = 0x646D0000,
      87             :     UNITLABEL_LI = 0x6C690000,
      88             :     UNITLABEL_SLI = 0x736C6900,
      89             :     UNITLABEL_SP = 0x73700000,
      90             :     UNITLABEL_FT = 0x66740000,
      91             :     UNITLABEL_SFT = 0x73667400,
      92             :     UNITLABEL_YD = 0x79640000,
      93             :     UNITLABEL_SYD = 0x73796400,
      94             :     UNITLABEL_M = 0x6D000000,
      95             :     UNITLABEL_FATH = 0x66617468,
      96             :     UNITLABEL_R = 0x72000000,
      97             :     UNITLABEL_RD = UNITLABEL_R,
      98             :     UNITLABEL_DAM = 0x64416D00,
      99             :     UNITLABEL_DKM = UNITLABEL_DAM,
     100             :     UNITLABEL_CH = 0x63680000,
     101             :     UNITLABEL_SCH = 0x73636800,
     102             :     UNITLABEL_HM = 0x686D0000,
     103             :     UNITLABEL_F = 0x66000000,
     104             :     UNITLABEL_KM = 0x6B6D0000,
     105             :     UNITLABEL_MI = 0x6D690000,
     106             :     UNITLABEL_SMI = 0x736D6900,
     107             :     UNITLABEL_NMI = 0x6E6D6900,
     108             :     UNITLABEL_MEGAM = 0x4D6D0000,
     109             :     UNITLABEL_LS = 0x6C730000,
     110             :     UNITLABEL_GM = 0x476D0000,
     111             :     UNITLABEL_LM = 0x6C6D0000,
     112             :     UNITLABEL_AU = 0x41550000,
     113             :     UNITLABEL_TM = 0x546D0000,
     114             :     UNITLABEL_LHR = 0x6C687200,
     115             :     UNITLABEL_LD = 0x6C640000,
     116             :     UNITLABEL_PETAM = 0x506D0000,
     117             :     UNITLABEL_LY = 0x6C790000,
     118             :     UNITLABEL_PC = 0x70630000,
     119             :     UNITLABEL_EXAM = 0x456D0000,
     120             :     UNITLABEL_KLY = 0x6B6C7900,
     121             :     UNITLABEL_KPC = 0x6B706300,
     122             :     UNITLABEL_ZETTAM = 0x5A6D0000,
     123             :     UNITLABEL_MLY = 0x4D6C7900,
     124             :     UNITLABEL_MPC = 0x4D706300,
     125             :     UNITLABEL_YOTTAM = 0x596D0000
     126             : } UNITLABEL;
     127             : 
     128             : typedef struct
     129             : {
     130             :     const char *pszID;
     131             :     double dScale;
     132             :     UNITLABEL oemCode;
     133             : } measurement_unit;
     134             : 
     135             : constexpr double kdays_per_year = 365.25;
     136             : constexpr double kdLStoM = 299792458.0;
     137             : constexpr double kdLYtoM = kdLStoM * kdays_per_year * 24 * 60 * 60;
     138             : constexpr double kdInch = 0.3048 / 12;
     139             : constexpr double kPI = M_PI;
     140             : 
     141             : constexpr int kFirstLinearMeasureIdx = 9;
     142             : 
     143             : static const measurement_unit kUnits[] = {
     144             :     {"", 1.0, UNITLABEL_UNKNOWN},
     145             :     {"px", 1.0, UNITLABEL_PIXEL},
     146             :     {"%", 1.0, UNITLABEL_PERCENT},  // not actually used
     147             : 
     148             :     {"rad", 1.0, UNITLABEL_RADIAN},
     149             :     {"\xB0", kPI / 180.0, UNITLABEL_DEGREE},  // \xB0 is Unicode degree symbol
     150             :     {"d", kPI / 180.0, UNITLABEL_DEGREE},
     151             :     {"deg", kPI / 180.0, UNITLABEL_DEGREE},
     152             :     {"'", kPI / (60.0 * 180.0), UNITLABEL_ARCMINUTE},
     153             :     {"\"", kPI / (3600.0 * 180.0), UNITLABEL_ARCSECOND},
     154             : 
     155             :     {"ym", 1.0e-24, UNITLABEL_YM},
     156             :     {"zm", 1.0e-21, UNITLABEL_ZM},
     157             :     {"am", 1.0e-18, UNITLABEL_AM},
     158             :     {"fm", 1.0e-15, UNITLABEL_FM},
     159             :     {"pm", 1.0e-12, UNITLABEL_PM},
     160             :     {"A", 1.0e-10, UNITLABEL_A},
     161             :     {"nm", 1.0e-9, UNITLABEL_NM},
     162             :     {"u", 1.0e-6, UNITLABEL_U},
     163             :     {"um", 1.0e-6, UNITLABEL_UM},
     164             :     {"ppt", kdInch / 72.27, UNITLABEL_PPT},
     165             :     {"pt", kdInch / 72.0, UNITLABEL_PT},
     166             :     {"mm", 1.0e-3, UNITLABEL_MM},
     167             :     {"p", kdInch / 6.0, UNITLABEL_P},
     168             :     {"cm", 1.0e-2, UNITLABEL_CM},
     169             :     {"in", kdInch, UNITLABEL_IN},
     170             :     {"dft", 0.03048, UNITLABEL_DFT},
     171             :     {"dm", 0.1, UNITLABEL_DM},
     172             :     {"li", 0.2011684 /* GDAL 0.20116684023368047 ? */, UNITLABEL_LI},
     173             :     {"sli", 0.201168402336805, UNITLABEL_SLI},
     174             :     {"sp", 0.2286, UNITLABEL_SP},
     175             :     {"ft", 0.3048, UNITLABEL_FT},
     176             :     {"sft", 1200.0 / 3937.0, UNITLABEL_SFT},
     177             :     {"yd", 0.9144, UNITLABEL_YD},
     178             :     {"syd", 0.914401828803658, UNITLABEL_SYD},
     179             :     {"m", 1.0, UNITLABEL_M},
     180             :     {"fath", 1.8288, UNITLABEL_FATH},
     181             :     {"rd", 5.02921, UNITLABEL_RD},
     182             :     {"dam", 10.0, UNITLABEL_DAM},
     183             :     {"dkm", 10.0, UNITLABEL_DKM},
     184             :     {"ch", 20.1168 /* GDAL: 2.0116684023368047 ? */, UNITLABEL_CH},
     185             :     {"sch", 20.1168402336805, UNITLABEL_SCH},
     186             :     {"hm", 100.0, UNITLABEL_HM},
     187             :     {"f", 201.168, UNITLABEL_F},
     188             :     {"km", 1000.0, UNITLABEL_KM},
     189             :     {"mi", 1609.344, UNITLABEL_MI},
     190             :     {"smi", 1609.34721869444, UNITLABEL_SMI},
     191             :     {"nmi", 1853.0, UNITLABEL_NMI},
     192             :     {"Mm", 1.0e+6, UNITLABEL_MEGAM},
     193             :     {"ls", kdLStoM, UNITLABEL_LS},
     194             :     {"Gm", 1.0e+9, UNITLABEL_GM},
     195             :     {"lm", kdLStoM * 60, UNITLABEL_LM},
     196             :     {"AU", 8.317 * kdLStoM * 60, UNITLABEL_AU},
     197             :     {"Tm", 1.0e+12, UNITLABEL_TM},
     198             :     {"lhr", 60.0 * 60.0 * kdLStoM, UNITLABEL_LHR},
     199             :     {"ld", 24 * 60.0 * 60.0 * kdLStoM, UNITLABEL_LD},
     200             :     {"Pm", 1.0e+15, UNITLABEL_PETAM},
     201             :     {"ly", kdLYtoM, UNITLABEL_LY},
     202             :     {"pc", 3.2616 * kdLYtoM, UNITLABEL_PC},
     203             :     {"Em", 1.0e+18, UNITLABEL_EXAM},
     204             :     {"kly", 1.0e+3 * kdLYtoM, UNITLABEL_KLY},
     205             :     {"kpc", 3.2616 * 1.0e+3 * kdLYtoM, UNITLABEL_KPC},
     206             :     {"Zm", 1.0e+21, UNITLABEL_ZETTAM},
     207             :     {"Mly", 1.0e+6 * kdLYtoM, UNITLABEL_MLY},
     208             :     {"Mpc", 3.2616 * 1.0e+6 * kdLYtoM, UNITLABEL_MPC},
     209             :     {"Ym", 1.0e+24, UNITLABEL_YOTTAM}};
     210             : 
     211             : // ----------------------------------------------------------------
     212             : 
     213           0 : static bool approx_equal(double a, double b)
     214             : {
     215           0 :     const double epsilon = 1e-5;
     216           0 :     return fabs(a - b) <= epsilon;
     217             : }
     218             : 
     219             : // ----------------------------------------------------------------
     220             : 
     221             : class LevellerRasterBand;
     222             : 
     223             : class LevellerDataset final : public GDALPamDataset
     224             : {
     225             :     friend class LevellerRasterBand;
     226             :     friend class digital_axis;
     227             : 
     228             :     int m_version;
     229             : 
     230             :     char *m_pszFilename;
     231             :     OGRSpatialReference m_oSRS{};
     232             : 
     233             :     // char              m_szUnits[8];
     234             :     char m_szElevUnits[8];
     235             :     double m_dElevScale;  // physical-to-logical scaling.
     236             :     double m_dElevBase;   // logical offset.
     237             :     GDALGeoTransform m_gt{};
     238             :     // double            m_dMeasurePerPixel;
     239             :     double m_dLogSpan[2];
     240             : 
     241             :     VSILFILE *m_fp;
     242             :     vsi_l_offset m_nDataOffset;
     243             : 
     244             :     bool load_from_file(VSILFILE *, const char *);
     245             : 
     246             :     static bool locate_data(vsi_l_offset &, size_t &, VSILFILE *, const char *);
     247             :     static bool get(int &, VSILFILE *, const char *);
     248             : 
     249             :     static bool get(size_t &n, VSILFILE *fp, const char *psz)
     250             :     {
     251             :         return get((int &)n, fp, psz);
     252             :     }
     253             : 
     254             :     static bool get(double &, VSILFILE *, const char *);
     255             :     static bool get(char *, size_t, VSILFILE *, const char *);
     256             : 
     257             :     bool write_header();
     258             :     bool write_tag(const char *, int);
     259             :     bool write_tag(const char *, size_t);
     260             :     bool write_tag(const char *, double);
     261             :     bool write_tag(const char *, const char *);
     262             :     bool write_tag_start(const char *, size_t);
     263             :     bool write(int);
     264             :     bool write(size_t);
     265             :     bool write(double);
     266             :     bool write_byte(size_t);
     267             : 
     268             :     static const measurement_unit *get_uom(const char *);
     269             :     static const measurement_unit *get_uom(UNITLABEL);
     270             :     static const measurement_unit *get_uom(double);
     271             : 
     272             :     static bool convert_measure(double, double &, const char *pszUnitsFrom);
     273             :     bool make_local_coordsys(const char *pszName, const char *pszUnits);
     274             :     bool make_local_coordsys(const char *pszName, UNITLABEL);
     275             :     const char *code_to_id(UNITLABEL) const;
     276             :     UNITLABEL id_to_code(const char *) const;
     277             :     UNITLABEL meter_measure_to_code(double) const;
     278             :     bool compute_elev_scaling(const OGRSpatialReference &);
     279             :     void raw_to_proj(double, double, double &, double &) const;
     280             : 
     281             :   public:
     282             :     LevellerDataset();
     283             :     ~LevellerDataset() override;
     284             : 
     285             :     static GDALDataset *Open(GDALOpenInfo *);
     286             :     static int Identify(GDALOpenInfo *);
     287             :     static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
     288             :                                int nBandsIn, GDALDataType eType,
     289             :                                char **papszOptions);
     290             : 
     291             :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
     292             : 
     293             :     CPLErr SetGeoTransform(const GDALGeoTransform &gt) override;
     294             : 
     295             :     const OGRSpatialReference *GetSpatialRef() const override;
     296             :     CPLErr SetSpatialRef(const OGRSpatialReference *poSRS) override;
     297             : };
     298             : 
     299             : class digital_axis
     300             : {
     301             :   public:
     302           0 :     digital_axis() : m_eStyle(LEV_DA_PIXEL_SIZED), m_fixedEnd(0)
     303             :     {
     304           0 :         m_d[0] = 0.0;
     305           0 :         m_d[1] = 0.0;
     306           0 :     }
     307             : 
     308           0 :     bool get(LevellerDataset &ds, VSILFILE *fp, int n)
     309             :     {
     310             :         char szTag[32];
     311           0 :         snprintf(szTag, sizeof(szTag), "coordsys_da%d_style", n);
     312           0 :         if (!ds.get(m_eStyle, fp, szTag))
     313           0 :             return false;
     314           0 :         snprintf(szTag, sizeof(szTag), "coordsys_da%d_fixedend", n);
     315           0 :         if (!ds.get(m_fixedEnd, fp, szTag))
     316           0 :             return false;
     317           0 :         snprintf(szTag, sizeof(szTag), "coordsys_da%d_v0", n);
     318           0 :         if (!ds.get(m_d[0], fp, szTag))
     319           0 :             return false;
     320           0 :         snprintf(szTag, sizeof(szTag), "coordsys_da%d_v1", n);
     321           0 :         if (!ds.get(m_d[1], fp, szTag))
     322           0 :             return false;
     323           0 :         return true;
     324             :     }
     325             : 
     326           0 :     double origin(size_t pixels) const
     327             :     {
     328           0 :         if (m_fixedEnd == 1)
     329             :         {
     330           0 :             switch (m_eStyle)
     331             :             {
     332           0 :                 case LEV_DA_SIZED:
     333           0 :                     return m_d[1] + m_d[0];
     334             : 
     335           0 :                 case LEV_DA_PIXEL_SIZED:
     336           0 :                     return m_d[1] + (m_d[0] * (pixels - 1));
     337             :             }
     338             :         }
     339           0 :         return m_d[0];
     340             :     }
     341             : 
     342           0 :     double scaling(size_t pixels) const
     343             :     {
     344           0 :         CPLAssert(pixels > 1);
     345           0 :         if (m_eStyle == LEV_DA_PIXEL_SIZED)
     346           0 :             return m_d[1 - m_fixedEnd];
     347             : 
     348           0 :         return this->length(static_cast<int>(pixels)) / (pixels - 1);
     349             :     }
     350             : 
     351           0 :     double length(int pixels) const
     352             :     {
     353             :         // Return the signed length of the axis.
     354             : 
     355           0 :         switch (m_eStyle)
     356             :         {
     357           0 :             case LEV_DA_POSITIONED:
     358           0 :                 return m_d[1] - m_d[0];
     359             : 
     360           0 :             case LEV_DA_SIZED:
     361           0 :                 return m_d[1 - m_fixedEnd];
     362             : 
     363           0 :             case LEV_DA_PIXEL_SIZED:
     364           0 :                 return m_d[1 - m_fixedEnd] * (pixels - 1);
     365             :         }
     366           0 :         CPLAssert(false);
     367             :         return 0.0;
     368             :     }
     369             : 
     370             :   protected:
     371             :     int m_eStyle;
     372             :     int m_fixedEnd;
     373             :     double m_d[2];
     374             : };
     375             : 
     376             : /************************************************************************/
     377             : /* ==================================================================== */
     378             : /*                            LevellerRasterBand                        */
     379             : /* ==================================================================== */
     380             : /************************************************************************/
     381             : 
     382             : class LevellerRasterBand final : public GDALPamRasterBand
     383             : {
     384             :     friend class LevellerDataset;
     385             : 
     386             :     float *m_pLine;
     387             :     bool m_bFirstTime;
     388             : 
     389             :   public:
     390             :     explicit LevellerRasterBand(LevellerDataset *);
     391             :     ~LevellerRasterBand() override;
     392             : 
     393             :     bool Init();
     394             : 
     395             :     // Geomeasure support.
     396             :     const char *GetUnitType() override;
     397             :     double GetScale(int *pbSuccess = nullptr) override;
     398             :     double GetOffset(int *pbSuccess = nullptr) override;
     399             : 
     400             :     CPLErr IReadBlock(int, int, void *) override;
     401             :     CPLErr IWriteBlock(int, int, void *) override;
     402             :     CPLErr SetUnitType(const char *) override;
     403             : };
     404             : 
     405             : /************************************************************************/
     406             : /*                         LevellerRasterBand()                         */
     407             : /************************************************************************/
     408             : 
     409           2 : LevellerRasterBand::LevellerRasterBand(LevellerDataset *poDSIn)
     410           2 :     : m_pLine(nullptr), m_bFirstTime(true)
     411             : {
     412           2 :     poDS = poDSIn;
     413           2 :     nBand = 1;
     414             : 
     415           2 :     eDataType = GDT_Float32;
     416             : 
     417           2 :     nBlockXSize = poDS->GetRasterXSize();
     418           2 :     nBlockYSize = 1;  // poDS->GetRasterYSize();
     419           2 : }
     420             : 
     421             : /************************************************************************/
     422             : /*                           Init()                                     */
     423             : /************************************************************************/
     424             : 
     425           2 : bool LevellerRasterBand::Init()
     426             : {
     427           2 :     m_pLine = reinterpret_cast<float *>(
     428           2 :         VSI_MALLOC2_VERBOSE(sizeof(float), nBlockXSize));
     429           2 :     return m_pLine != nullptr;
     430             : }
     431             : 
     432           4 : LevellerRasterBand::~LevellerRasterBand()
     433             : {
     434           2 :     CPLFree(m_pLine);
     435           4 : }
     436             : 
     437             : /************************************************************************/
     438             : /*                             IWriteBlock()                            */
     439             : /************************************************************************/
     440             : 
     441           0 : CPLErr LevellerRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff,
     442             :                                        int nBlockYOff, void *pImage)
     443             : {
     444           0 :     CPLAssert(nBlockXOff == 0);
     445           0 :     CPLAssert(pImage != nullptr);
     446           0 :     CPLAssert(m_pLine != nullptr);
     447             : 
     448             :     /*  #define sgn(_n) ((_n) < 0 ? -1 : ((_n) > 0 ? 1 : 0) )
     449             :         #define sround(_f)                          \
     450             :         (int)((_f) + (0.5 * sgn(_f)))
     451             :     */
     452           0 :     const size_t pixelsize = sizeof(float);
     453             : 
     454           0 :     LevellerDataset &ds = *cpl::down_cast<LevellerDataset *>(poDS);
     455           0 :     if (m_bFirstTime)
     456             :     {
     457           0 :         m_bFirstTime = false;
     458           0 :         if (!ds.write_header())
     459           0 :             return CE_Failure;
     460           0 :         ds.m_nDataOffset = VSIFTellL(ds.m_fp);
     461             :     }
     462           0 :     const size_t rowbytes = nBlockXSize * pixelsize;
     463           0 :     const float *pfImage = reinterpret_cast<float *>(pImage);
     464             : 
     465           0 :     if (0 ==
     466           0 :         VSIFSeekL(ds.m_fp, ds.m_nDataOffset + nBlockYOff * rowbytes, SEEK_SET))
     467             :     {
     468           0 :         for (size_t x = 0; x < (size_t)nBlockXSize; x++)
     469             :         {
     470             :             // Convert logical elevations to physical.
     471           0 :             m_pLine[x] = static_cast<float>((pfImage[x] - ds.m_dElevBase) /
     472           0 :                                             ds.m_dElevScale);
     473             :         }
     474             : 
     475             : #ifdef CPL_MSB
     476             :         GDALSwapWords(m_pLine, pixelsize, nBlockXSize, pixelsize);
     477             : #endif
     478           0 :         if (1 == VSIFWriteL(m_pLine, rowbytes, 1, ds.m_fp))
     479           0 :             return CE_None;
     480             :     }
     481             : 
     482           0 :     return CE_Failure;
     483             : }
     484             : 
     485           0 : CPLErr LevellerRasterBand::SetUnitType(const char *psz)
     486             : {
     487           0 :     LevellerDataset &ds = *cpl::down_cast<LevellerDataset *>(poDS);
     488             : 
     489           0 :     if (strlen(psz) >= sizeof(ds.m_szElevUnits))
     490           0 :         return CE_Failure;
     491             : 
     492           0 :     strcpy(ds.m_szElevUnits, psz);
     493             : 
     494           0 :     return CE_None;
     495             : }
     496             : 
     497             : /************************************************************************/
     498             : /*                             IReadBlock()                             */
     499             : /************************************************************************/
     500             : 
     501          96 : CPLErr LevellerRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
     502             :                                       void *pImage)
     503             : 
     504             : {
     505             :     CPLAssert(sizeof(float) == sizeof(GInt32));
     506          96 :     CPLAssert(nBlockXOff == 0);
     507          96 :     CPLAssert(pImage != nullptr);
     508             : 
     509          96 :     LevellerDataset *poGDS = cpl::down_cast<LevellerDataset *>(poDS);
     510             : 
     511             :     /* -------------------------------------------------------------------- */
     512             :     /*      Seek to scanline.                                               */
     513             :     /* -------------------------------------------------------------------- */
     514          96 :     const size_t rowbytes = nBlockXSize * sizeof(float);
     515             : 
     516          96 :     if (0 != VSIFSeekL(poGDS->m_fp,
     517          96 :                        poGDS->m_nDataOffset + nBlockYOff * rowbytes, SEEK_SET))
     518             :     {
     519           0 :         CPLError(CE_Failure, CPLE_FileIO, "Leveller seek failed: %s",
     520           0 :                  VSIStrerror(errno));
     521           0 :         return CE_Failure;
     522             :     }
     523             : 
     524             :     /* -------------------------------------------------------------------- */
     525             :     /*      Read the scanline into the image buffer.                        */
     526             :     /* -------------------------------------------------------------------- */
     527             : 
     528          96 :     if (VSIFReadL(pImage, rowbytes, 1, poGDS->m_fp) != 1)
     529             :     {
     530           0 :         CPLError(CE_Failure, CPLE_FileIO, "Leveller read failed: %s",
     531           0 :                  VSIStrerror(errno));
     532           0 :         return CE_Failure;
     533             :     }
     534             : 
     535             : /* -------------------------------------------------------------------- */
     536             : /*      Swap on MSB platforms.                                          */
     537             : /* -------------------------------------------------------------------- */
     538             : #ifdef CPL_MSB
     539             :     GDALSwapWords(pImage, 4, nRasterXSize, 4);
     540             : #endif
     541             : 
     542             :     /* -------------------------------------------------------------------- */
     543             :     /*      Convert from legacy-format fixed-point if necessary.            */
     544             :     /* -------------------------------------------------------------------- */
     545          96 :     float *pf = reinterpret_cast<float *>(pImage);
     546             : 
     547          96 :     if (poGDS->m_version < 6)
     548             :     {
     549           0 :         GInt32 *pi = reinterpret_cast<int *>(pImage);
     550           0 :         for (size_t i = 0; i < (size_t)nBlockXSize; i++)
     551           0 :             pf[i] = static_cast<float>(pi[i]) / 65536;
     552             :     }
     553             : 
     554             : /* -------------------------------------------------------------------- */
     555             : /*      Convert raw elevations to realworld elevs.                      */
     556             : /* -------------------------------------------------------------------- */
     557             : #if 0
     558             :     for(size_t i = 0; i < nBlockXSize; i++)
     559             :         pf[i] *= poGDS->m_dWorldscale; //this->GetScale();
     560             : #endif
     561             : 
     562          96 :     return CE_None;
     563             : }
     564             : 
     565             : /************************************************************************/
     566             : /*                            GetUnitType()                             */
     567             : /************************************************************************/
     568           0 : const char *LevellerRasterBand::GetUnitType()
     569             : {
     570             :     // Return elevation units.
     571             : 
     572           0 :     LevellerDataset *poGDS = cpl::down_cast<LevellerDataset *>(poDS);
     573             : 
     574           0 :     return poGDS->m_szElevUnits;
     575             : }
     576             : 
     577             : /************************************************************************/
     578             : /*                              GetScale()                              */
     579             : /************************************************************************/
     580             : 
     581           0 : double LevellerRasterBand::GetScale(int *pbSuccess)
     582             : {
     583           0 :     LevellerDataset *poGDS = cpl::down_cast<LevellerDataset *>(poDS);
     584           0 :     if (pbSuccess != nullptr)
     585           0 :         *pbSuccess = TRUE;
     586           0 :     return poGDS->m_dElevScale;
     587             : }
     588             : 
     589             : /************************************************************************/
     590             : /*                             GetOffset()                              */
     591             : /************************************************************************/
     592             : 
     593           0 : double LevellerRasterBand::GetOffset(int *pbSuccess)
     594             : {
     595           0 :     LevellerDataset *poGDS = cpl::down_cast<LevellerDataset *>(poDS);
     596           0 :     if (pbSuccess != nullptr)
     597           0 :         *pbSuccess = TRUE;
     598           0 :     return poGDS->m_dElevBase;
     599             : }
     600             : 
     601             : /************************************************************************/
     602             : /* ==================================================================== */
     603             : /*                              LevellerDataset                         */
     604             : /* ==================================================================== */
     605             : /************************************************************************/
     606             : 
     607             : /************************************************************************/
     608             : /*                          LevellerDataset()                           */
     609             : /************************************************************************/
     610             : 
     611           6 : LevellerDataset::LevellerDataset()
     612             :     : m_version(0), m_pszFilename(nullptr), m_dElevScale(), m_dElevBase(),
     613           6 :       m_fp(nullptr), m_nDataOffset()
     614             : {
     615           6 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     616           6 :     memset(m_szElevUnits, 0, sizeof(m_szElevUnits));
     617           6 :     memset(m_dLogSpan, 0, sizeof(m_dLogSpan));
     618           6 : }
     619             : 
     620             : /************************************************************************/
     621             : /*                          ~LevellerDataset()                          */
     622             : /************************************************************************/
     623             : 
     624          12 : LevellerDataset::~LevellerDataset()
     625             : {
     626           6 :     FlushCache(true);
     627             : 
     628           6 :     CPLFree(m_pszFilename);
     629             : 
     630           6 :     if (m_fp != nullptr)
     631           4 :         VSIFCloseL(m_fp);
     632          12 : }
     633             : 
     634           0 : static double degrees_to_radians(double d)
     635             : {
     636           0 :     return d * (M_PI / 180);
     637             : }
     638             : 
     639           0 : static double average(double a, double b)
     640             : {
     641           0 :     return 0.5 * (a + b);
     642             : }
     643             : 
     644           0 : void LevellerDataset::raw_to_proj(double x, double y, double &xp,
     645             :                                   double &yp) const
     646             : {
     647           0 :     xp = x * m_gt[1] + m_gt[0];
     648           0 :     yp = y * m_gt[5] + m_gt[3];
     649           0 : }
     650             : 
     651           0 : bool LevellerDataset::compute_elev_scaling(const OGRSpatialReference &sr)
     652             : {
     653           0 :     const char *pszGroundUnits = nullptr;
     654             : 
     655           0 :     if (!sr.IsGeographic())
     656             :     {
     657             :         // For projected or local CS, the elev scale is
     658             :         // the average ground scale.
     659           0 :         m_dElevScale = average(m_gt[1], m_gt[5]);
     660             : 
     661           0 :         const double dfLinear = sr.GetLinearUnits();
     662           0 :         const measurement_unit *pu = this->get_uom(dfLinear);
     663           0 :         if (pu == nullptr)
     664           0 :             return false;
     665             : 
     666           0 :         pszGroundUnits = pu->pszID;
     667             :     }
     668             :     else
     669             :     {
     670           0 :         pszGroundUnits = "m";
     671             : 
     672           0 :         const double kdEarthCircumPolar = 40007849;
     673           0 :         const double kdEarthCircumEquat = 40075004;
     674             : 
     675           0 :         const double xr = 0.5 * nRasterXSize;
     676           0 :         const double yr = 0.5 * nRasterYSize;
     677             : 
     678             :         double xg[2], yg[2];
     679           0 :         raw_to_proj(xr, yr, xg[0], yg[0]);
     680           0 :         raw_to_proj(xr + 1, yr + 1, xg[1], yg[1]);
     681             : 
     682             :         // The earths' circumference shrinks using a sin()
     683             :         // curve as we go up in latitude.
     684             :         const double dLatCircum =
     685           0 :             kdEarthCircumEquat * sin(degrees_to_radians(90.0 - yg[0]));
     686             : 
     687             :         // Derive meter distance between geolongitudes
     688             :         // in xg[0] and xg[1].
     689           0 :         const double dx = fabs(xg[1] - xg[0]) / 360.0 * dLatCircum;
     690           0 :         const double dy = fabs(yg[1] - yg[0]) / 360.0 * kdEarthCircumPolar;
     691             : 
     692           0 :         m_dElevScale = average(dx, dy);
     693             :     }
     694             : 
     695           0 :     m_dElevBase = m_dLogSpan[0];
     696             : 
     697             :     // Convert from ground units to elev units.
     698           0 :     const measurement_unit *puG = this->get_uom(pszGroundUnits);
     699           0 :     const measurement_unit *puE = this->get_uom(m_szElevUnits);
     700             : 
     701           0 :     if (puG == nullptr || puE == nullptr)
     702           0 :         return false;
     703             : 
     704           0 :     const double g_to_e = puG->dScale / puE->dScale;
     705             : 
     706           0 :     m_dElevScale *= g_to_e;
     707           0 :     return true;
     708             : }
     709             : 
     710           0 : bool LevellerDataset::write_header()
     711             : {
     712             :     char szHeader[5];
     713           0 :     strcpy(szHeader, "trrn");
     714           0 :     szHeader[4] = 7;  // TER v7 introduced w/ Lev 2.6.
     715             : 
     716           0 :     if (1 != VSIFWriteL(szHeader, 5, 1, m_fp) ||
     717           0 :         !this->write_tag("hf_w", (size_t)nRasterXSize) ||
     718           0 :         !this->write_tag("hf_b", (size_t)nRasterYSize))
     719             :     {
     720           0 :         CPLError(CE_Failure, CPLE_FileIO, "Could not write header");
     721           0 :         return false;
     722             :     }
     723             : 
     724           0 :     m_dElevBase = 0.0;
     725           0 :     m_dElevScale = 1.0;
     726             : 
     727           0 :     if (m_oSRS.IsEmpty())
     728             :     {
     729           0 :         write_tag("csclass", LEV_COORDSYS_RASTER);
     730             :     }
     731             :     else
     732             :     {
     733           0 :         char *pszWkt = nullptr;
     734           0 :         m_oSRS.exportToWkt(&pszWkt);
     735           0 :         if (pszWkt)
     736           0 :             write_tag("coordsys_wkt", pszWkt);
     737           0 :         CPLFree(pszWkt);
     738           0 :         const UNITLABEL units_elev = this->id_to_code(m_szElevUnits);
     739             : 
     740           0 :         const int bHasECS =
     741           0 :             (units_elev != UNITLABEL_PIXEL && units_elev != UNITLABEL_UNKNOWN);
     742             : 
     743           0 :         write_tag("coordsys_haselevm", bHasECS);
     744             : 
     745           0 :         if (bHasECS)
     746             :         {
     747           0 :             if (!this->compute_elev_scaling(m_oSRS))
     748           0 :                 return false;
     749             : 
     750             :             // Raw-to-real units scaling.
     751           0 :             write_tag("coordsys_em_scale", m_dElevScale);
     752             : 
     753             :             // Elev offset, in real units.
     754           0 :             write_tag("coordsys_em_base", m_dElevBase);
     755           0 :             write_tag("coordsys_em_units", units_elev);
     756             :         }
     757             : 
     758           0 :         if (m_oSRS.IsLocal())
     759             :         {
     760           0 :             write_tag("csclass", LEV_COORDSYS_LOCAL);
     761             : 
     762           0 :             const double dfLinear = m_oSRS.GetLinearUnits();
     763           0 :             const int n = this->meter_measure_to_code(dfLinear);
     764           0 :             write_tag("coordsys_units", n);
     765             :         }
     766             :         else
     767             :         {
     768           0 :             write_tag("csclass", LEV_COORDSYS_GEO);
     769             :         }
     770             : 
     771           0 :         if (m_gt[2] != 0.0 || m_gt[4] != 0.0)
     772             :         {
     773           0 :             CPLError(CE_Failure, CPLE_IllegalArg,
     774             :                      "Cannot handle rotated geotransform");
     775           0 :             return false;
     776             :         }
     777             : 
     778             :         // todo: GDAL gridpost spacing is based on extent / rastersize
     779             :         // instead of extent / (rastersize-1) like Leveller.
     780             :         // We need to look into this and adjust accordingly.
     781             : 
     782             :         // Write north-south digital axis.
     783           0 :         write_tag("coordsys_da0_style", LEV_DA_PIXEL_SIZED);
     784           0 :         write_tag("coordsys_da0_fixedend", 0);
     785           0 :         write_tag("coordsys_da0_v0", m_gt[3]);
     786           0 :         write_tag("coordsys_da0_v1", m_gt[5]);
     787             : 
     788             :         // Write east-west digital axis.
     789           0 :         write_tag("coordsys_da1_style", LEV_DA_PIXEL_SIZED);
     790           0 :         write_tag("coordsys_da1_fixedend", 0);
     791           0 :         write_tag("coordsys_da1_v0", m_gt[0]);
     792           0 :         write_tag("coordsys_da1_v1", m_gt[1]);
     793             :     }
     794             : 
     795           0 :     this->write_tag_start("hf_data",
     796           0 :                           sizeof(float) * nRasterXSize * nRasterYSize);
     797             : 
     798           0 :     return true;
     799             : }
     800             : 
     801             : /************************************************************************/
     802             : /*                          SetGeoTransform()                           */
     803             : /************************************************************************/
     804             : 
     805           0 : CPLErr LevellerDataset::SetGeoTransform(const GDALGeoTransform &gt)
     806             : {
     807           0 :     m_gt = gt;
     808             : 
     809           0 :     return CE_None;
     810             : }
     811             : 
     812             : /************************************************************************/
     813             : /*                           SetSpatialRef()                            */
     814             : /************************************************************************/
     815             : 
     816           0 : CPLErr LevellerDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
     817             : {
     818           0 :     m_oSRS.Clear();
     819           0 :     if (poSRS)
     820           0 :         m_oSRS = *poSRS;
     821             : 
     822           0 :     return CE_None;
     823             : }
     824             : 
     825             : /************************************************************************/
     826             : /*                           Create()                                   */
     827             : /************************************************************************/
     828          49 : GDALDataset *LevellerDataset::Create(const char *pszFilename, int nXSize,
     829             :                                      int nYSize, int nBandsIn,
     830             :                                      GDALDataType eType, char **papszOptions)
     831             : {
     832          49 :     if (nBandsIn != 1)
     833             :     {
     834          22 :         CPLError(CE_Failure, CPLE_IllegalArg, "Band count must be 1");
     835          22 :         return nullptr;
     836             :     }
     837             : 
     838          27 :     if (eType != GDT_Float32)
     839             :     {
     840          23 :         CPLError(CE_Failure, CPLE_IllegalArg, "Pixel type must be Float32");
     841          23 :         return nullptr;
     842             :     }
     843             : 
     844           4 :     if (nXSize < 2 || nYSize < 2)
     845             :     {
     846           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
     847             :                  "One or more raster dimensions too small");
     848           0 :         return nullptr;
     849             :     }
     850             : 
     851           4 :     LevellerDataset *poDS = new LevellerDataset;
     852             : 
     853           4 :     poDS->eAccess = GA_Update;
     854             : 
     855           4 :     poDS->m_pszFilename = CPLStrdup(pszFilename);
     856             : 
     857           4 :     poDS->m_fp = VSIFOpenL(pszFilename, "wb+");
     858             : 
     859           4 :     if (poDS->m_fp == nullptr)
     860             :     {
     861           2 :         CPLError(CE_Failure, CPLE_OpenFailed,
     862             :                  "Attempt to create file `%s' failed.", pszFilename);
     863           2 :         delete poDS;
     864           2 :         return nullptr;
     865             :     }
     866             : 
     867             :     // Header will be written the first time IWriteBlock
     868             :     // is called.
     869             : 
     870           2 :     poDS->nRasterXSize = nXSize;
     871           2 :     poDS->nRasterYSize = nYSize;
     872             : 
     873           2 :     const char *pszValue = CSLFetchNameValue(papszOptions, "MINUSERPIXELVALUE");
     874           2 :     if (pszValue != nullptr)
     875           0 :         poDS->m_dLogSpan[0] = CPLAtof(pszValue);
     876             :     else
     877             :     {
     878           2 :         delete poDS;
     879           2 :         CPLError(CE_Failure, CPLE_IllegalArg,
     880             :                  "MINUSERPIXELVALUE must be specified.");
     881           2 :         return nullptr;
     882             :     }
     883             : 
     884           0 :     pszValue = CSLFetchNameValue(papszOptions, "MAXUSERPIXELVALUE");
     885           0 :     if (pszValue != nullptr)
     886           0 :         poDS->m_dLogSpan[1] = CPLAtof(pszValue);
     887             : 
     888           0 :     if (poDS->m_dLogSpan[1] < poDS->m_dLogSpan[0])
     889             :     {
     890           0 :         double t = poDS->m_dLogSpan[0];
     891           0 :         poDS->m_dLogSpan[0] = poDS->m_dLogSpan[1];
     892           0 :         poDS->m_dLogSpan[1] = t;
     893             :     }
     894             : 
     895             :     // --------------------------------------------------------------------
     896             :     //      Instance a band.
     897             :     // --------------------------------------------------------------------
     898           0 :     LevellerRasterBand *poBand = new LevellerRasterBand(poDS);
     899           0 :     poDS->SetBand(1, poBand);
     900             : 
     901           0 :     if (!poBand->Init())
     902             :     {
     903           0 :         delete poDS;
     904           0 :         return nullptr;
     905             :     }
     906             : 
     907           0 :     return poDS;
     908             : }
     909             : 
     910           0 : bool LevellerDataset::write_byte(size_t n)
     911             : {
     912           0 :     unsigned char uch = static_cast<unsigned char>(n);
     913           0 :     return 1 == VSIFWriteL(&uch, 1, 1, m_fp);
     914             : }
     915             : 
     916           0 : bool LevellerDataset::write(int n)
     917             : {
     918           0 :     CPL_LSBPTR32(&n);
     919           0 :     return 1 == VSIFWriteL(&n, sizeof(n), 1, m_fp);
     920             : }
     921             : 
     922           0 : bool LevellerDataset::write(size_t n)
     923             : {
     924           0 :     GUInt32 n32 = (GUInt32)n;
     925           0 :     CPL_LSBPTR32(&n32);
     926           0 :     return 1 == VSIFWriteL(&n32, sizeof(n32), 1, m_fp);
     927             : }
     928             : 
     929           0 : bool LevellerDataset::write(double d)
     930             : {
     931           0 :     CPL_LSBPTR64(&d);
     932           0 :     return 1 == VSIFWriteL(&d, sizeof(d), 1, m_fp);
     933             : }
     934             : 
     935           0 : bool LevellerDataset::write_tag_start(const char *pszTag, size_t n)
     936             : {
     937           0 :     if (this->write_byte(strlen(pszTag)))
     938             :     {
     939           0 :         return (1 == VSIFWriteL(pszTag, strlen(pszTag), 1, m_fp) &&
     940           0 :                 this->write(n));
     941             :     }
     942             : 
     943           0 :     return false;
     944             : }
     945             : 
     946           0 : bool LevellerDataset::write_tag(const char *pszTag, int n)
     947             : {
     948           0 :     return (this->write_tag_start(pszTag, sizeof(n)) && this->write(n));
     949             : }
     950             : 
     951           0 : bool LevellerDataset::write_tag(const char *pszTag, size_t n)
     952             : {
     953           0 :     return (this->write_tag_start(pszTag, sizeof(n)) && this->write(n));
     954             : }
     955             : 
     956           0 : bool LevellerDataset::write_tag(const char *pszTag, double d)
     957             : {
     958           0 :     return (this->write_tag_start(pszTag, sizeof(d)) && this->write(d));
     959             : }
     960             : 
     961           0 : bool LevellerDataset::write_tag(const char *pszTag, const char *psz)
     962             : {
     963           0 :     constexpr size_t kMaxTagNameLen = 63;
     964           0 :     CPLAssert(strlen(pszTag) <= kMaxTagNameLen);
     965             : 
     966             :     char sz[kMaxTagNameLen + 2];
     967           0 :     snprintf(sz, sizeof(sz), "%sl", pszTag);
     968           0 :     const size_t len = strlen(psz);
     969             : 
     970           0 :     if (len > 0 && this->write_tag(sz, len))
     971             :     {
     972           0 :         snprintf(sz, sizeof(sz), "%sd", pszTag);
     973           0 :         this->write_tag_start(sz, len);
     974           0 :         return 1 == VSIFWriteL(psz, len, 1, m_fp);
     975             :     }
     976           0 :     return false;
     977             : }
     978             : 
     979          10 : bool LevellerDataset::locate_data(vsi_l_offset &offset, size_t &len,
     980             :                                   VSILFILE *fp, const char *pszTag)
     981             : {
     982             :     // Locate the file offset of the desired tag's data.
     983             :     // If it is not available, return false.
     984             :     // If the tag is found, leave the filemark at the
     985             :     // start of its data.
     986             : 
     987          10 :     if (0 != VSIFSeekL(fp, 5, SEEK_SET))
     988           0 :         return false;
     989             : 
     990          10 :     const int kMaxDescLen = 64;
     991             :     for (;;)
     992             :     {
     993             :         unsigned char c;
     994        1498 :         if (1 != VSIFReadL(&c, sizeof(c), 1, fp))
     995          10 :             return false;
     996             : 
     997        1498 :         const size_t descriptorLen = c;
     998        1498 :         if (descriptorLen == 0 || descriptorLen > (size_t)kMaxDescLen)
     999           0 :             return false;
    1000             : 
    1001             :         char descriptor[kMaxDescLen + 1];
    1002        1498 :         if (1 != VSIFReadL(descriptor, descriptorLen, 1, fp))
    1003           0 :             return false;
    1004             : 
    1005             :         GUInt32 datalen;
    1006        1498 :         if (1 != VSIFReadL(&datalen, sizeof(datalen), 1, fp))
    1007           0 :             return false;
    1008             : 
    1009        1498 :         CPL_LSBPTR32(&datalen);
    1010        1498 :         descriptor[descriptorLen] = 0;
    1011        1498 :         if (str_equal(descriptor, pszTag))
    1012             :         {
    1013          10 :             len = (size_t)datalen;
    1014          10 :             offset = VSIFTellL(fp);
    1015          10 :             return true;
    1016             :         }
    1017             :         else
    1018             :         {
    1019             :             // Seek to next tag.
    1020        1488 :             if (0 != VSIFSeekL(fp, (vsi_l_offset)datalen, SEEK_CUR))
    1021           0 :                 return false;
    1022             :         }
    1023        1488 :     }
    1024             : }
    1025             : 
    1026             : /************************************************************************/
    1027             : /*                                get()                                 */
    1028             : /************************************************************************/
    1029             : 
    1030           4 : bool LevellerDataset::get(int &n, VSILFILE *fp, const char *psz)
    1031             : {
    1032             :     vsi_l_offset offset;
    1033             :     size_t len;
    1034             : 
    1035           4 :     if (locate_data(offset, len, fp, psz))
    1036             :     {
    1037             :         GInt32 value;
    1038           4 :         if (1 == VSIFReadL(&value, sizeof(value), 1, fp))
    1039             :         {
    1040           4 :             CPL_LSBPTR32(&value);
    1041           4 :             n = static_cast<int>(value);
    1042           4 :             return true;
    1043             :         }
    1044             :     }
    1045           0 :     return false;
    1046             : }
    1047             : 
    1048             : /************************************************************************/
    1049             : /*                                get()                                 */
    1050             : /************************************************************************/
    1051             : 
    1052           2 : bool LevellerDataset::get(double &d, VSILFILE *fp, const char *pszTag)
    1053             : {
    1054             :     vsi_l_offset offset;
    1055             :     size_t len;
    1056             : 
    1057           2 :     if (locate_data(offset, len, fp, pszTag))
    1058             :     {
    1059           2 :         if (1 == VSIFReadL(&d, sizeof(d), 1, fp))
    1060             :         {
    1061           2 :             CPL_LSBPTR64(&d);
    1062           2 :             return true;
    1063             :         }
    1064             :     }
    1065           0 :     return false;
    1066             : }
    1067             : 
    1068             : /************************************************************************/
    1069             : /*                                get()                                 */
    1070             : /************************************************************************/
    1071           2 : bool LevellerDataset::get(char *pszValue, size_t maxchars, VSILFILE *fp,
    1072             :                           const char *pszTag)
    1073             : {
    1074             :     char szTag[65];
    1075             : 
    1076             :     // We can assume 8-bit encoding, so just go straight
    1077             :     // to the *_d tag.
    1078           2 :     snprintf(szTag, sizeof(szTag), "%sd", pszTag);
    1079             : 
    1080             :     vsi_l_offset offset;
    1081             :     size_t len;
    1082             : 
    1083           2 :     if (locate_data(offset, len, fp, szTag))
    1084             :     {
    1085           2 :         if (len > maxchars)
    1086           0 :             return false;
    1087             : 
    1088           2 :         if (1 == VSIFReadL(pszValue, len, 1, fp))
    1089             :         {
    1090           2 :             pszValue[len] = 0;  // terminate C-string
    1091           2 :             return true;
    1092             :         }
    1093             :     }
    1094             : 
    1095           0 :     return false;
    1096             : }
    1097             : 
    1098           0 : UNITLABEL LevellerDataset::meter_measure_to_code(double dM) const
    1099             : {
    1100             :     // Convert a meter conversion factor to its UOM OEM code.
    1101             :     // If the factor is close to the approximation margin, then
    1102             :     // require exact equality, otherwise be loose.
    1103             : 
    1104           0 :     const measurement_unit *pu = this->get_uom(dM);
    1105           0 :     return pu != nullptr ? pu->oemCode : UNITLABEL_UNKNOWN;
    1106             : }
    1107             : 
    1108           0 : UNITLABEL LevellerDataset::id_to_code(const char *pszUnits) const
    1109             : {
    1110             :     // Convert a readable UOM to its OEM code.
    1111             : 
    1112           0 :     const measurement_unit *pu = this->get_uom(pszUnits);
    1113           0 :     return pu != nullptr ? pu->oemCode : UNITLABEL_UNKNOWN;
    1114             : }
    1115             : 
    1116           0 : const char *LevellerDataset::code_to_id(UNITLABEL code) const
    1117             : {
    1118             :     // Convert a measurement unit's OEM ID to its readable ID.
    1119             : 
    1120           0 :     const measurement_unit *pu = this->get_uom(code);
    1121           0 :     return pu != nullptr ? pu->pszID : nullptr;
    1122             : }
    1123             : 
    1124           0 : const measurement_unit *LevellerDataset::get_uom(const char *pszUnits)
    1125             : {
    1126           0 :     for (size_t i = 0; i < CPL_ARRAYSIZE(kUnits); i++)
    1127             :     {
    1128           0 :         if (strcmp(pszUnits, kUnits[i].pszID) == 0)
    1129           0 :             return &kUnits[i];
    1130             :     }
    1131           0 :     CPLError(CE_Failure, CPLE_AppDefined, "Unknown measurement units: %s",
    1132             :              pszUnits);
    1133           0 :     return nullptr;
    1134             : }
    1135             : 
    1136           0 : const measurement_unit *LevellerDataset::get_uom(UNITLABEL code)
    1137             : {
    1138           0 :     for (size_t i = 0; i < CPL_ARRAYSIZE(kUnits); i++)
    1139             :     {
    1140           0 :         if (kUnits[i].oemCode == code)
    1141           0 :             return &kUnits[i];
    1142             :     }
    1143           0 :     CPLError(CE_Failure, CPLE_AppDefined, "Unknown measurement unit code: %08x",
    1144             :              code);
    1145           0 :     return nullptr;
    1146             : }
    1147             : 
    1148           0 : const measurement_unit *LevellerDataset::get_uom(double dM)
    1149             : {
    1150           0 :     for (size_t i = kFirstLinearMeasureIdx; i < CPL_ARRAYSIZE(kUnits); i++)
    1151             :     {
    1152           0 :         if (dM >= 1.0e-4)
    1153             :         {
    1154           0 :             if (approx_equal(dM, kUnits[i].dScale))
    1155           0 :                 return &kUnits[i];
    1156             :         }
    1157           0 :         else if (dM == kUnits[i].dScale)
    1158           0 :             return &kUnits[i];
    1159             :     }
    1160           0 :     CPLError(CE_Failure, CPLE_AppDefined,
    1161             :              "Unknown measurement conversion factor: %f", dM);
    1162           0 :     return nullptr;
    1163             : }
    1164             : 
    1165             : /************************************************************************/
    1166             : /*                          convert_measure()                           */
    1167             : /************************************************************************/
    1168             : 
    1169           2 : bool LevellerDataset::convert_measure(double d, double &dResult,
    1170             :                                       const char *pszSpace)
    1171             : {
    1172             :     // Convert a measure to meters.
    1173             : 
    1174          42 :     for (size_t i = kFirstLinearMeasureIdx; i < CPL_ARRAYSIZE(kUnits); i++)
    1175             :     {
    1176          42 :         if (str_equal(pszSpace, kUnits[i].pszID))
    1177             :         {
    1178           2 :             dResult = d * kUnits[i].dScale;
    1179           2 :             return true;
    1180             :         }
    1181             :     }
    1182           0 :     CPLError(CE_Failure, CPLE_FileIO, "Unknown linear measurement unit: '%s'",
    1183             :              pszSpace);
    1184           0 :     return false;
    1185             : }
    1186             : 
    1187           2 : bool LevellerDataset::make_local_coordsys(const char *pszName,
    1188             :                                           const char *pszUnits)
    1189             : {
    1190           2 :     m_oSRS.SetLocalCS(pszName);
    1191             :     double d;
    1192           4 :     return (convert_measure(1.0, d, pszUnits) &&
    1193           4 :             OGRERR_NONE == m_oSRS.SetLinearUnits(pszUnits, d));
    1194             : }
    1195             : 
    1196           0 : bool LevellerDataset::make_local_coordsys(const char *pszName, UNITLABEL code)
    1197             : {
    1198           0 :     const char *pszUnitID = code_to_id(code);
    1199           0 :     return pszUnitID != nullptr && make_local_coordsys(pszName, pszUnitID);
    1200             : }
    1201             : 
    1202             : /************************************************************************/
    1203             : /*                            load_from_file()                            */
    1204             : /************************************************************************/
    1205             : 
    1206           2 : bool LevellerDataset::load_from_file(VSILFILE *file, const char *pszFilename)
    1207             : {
    1208             :     // get hf dimensions
    1209           2 :     if (!get(nRasterXSize, file, "hf_w"))
    1210             :     {
    1211           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
    1212             :                  "Cannot determine heightfield width.");
    1213           0 :         return false;
    1214             :     }
    1215             : 
    1216           2 :     if (!get(nRasterYSize, file, "hf_b"))
    1217             :     {
    1218           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
    1219             :                  "Cannot determine heightfield breadth.");
    1220           0 :         return false;
    1221             :     }
    1222             : 
    1223           2 :     if (nRasterXSize < 2 || nRasterYSize < 2)
    1224             :     {
    1225           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
    1226             :                  "Heightfield raster dimensions too small.");
    1227           0 :         return false;
    1228             :     }
    1229             : 
    1230             :     // Record start of pixel data
    1231             :     size_t datalen;
    1232           2 :     if (!locate_data(m_nDataOffset, datalen, file, "hf_data"))
    1233             :     {
    1234           0 :         CPLError(CE_Failure, CPLE_OpenFailed, "Cannot locate elevation data.");
    1235           0 :         return false;
    1236             :     }
    1237             : 
    1238             :     // Sanity check: do we have enough pixels?
    1239           2 :     if (static_cast<GUIntBig>(datalen) !=
    1240           2 :         static_cast<GUIntBig>(nRasterXSize) *
    1241           2 :             static_cast<GUIntBig>(nRasterYSize) * sizeof(float))
    1242             :     {
    1243           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
    1244             :                  "File does not have enough data.");
    1245           0 :         return false;
    1246             :     }
    1247             : 
    1248             :     // Defaults for raster coordsys.
    1249           2 :     m_gt = GDALGeoTransform();
    1250             : 
    1251           2 :     m_dElevScale = 1.0;
    1252           2 :     m_dElevBase = 0.0;
    1253           2 :     strcpy(m_szElevUnits, "");
    1254             : 
    1255           2 :     if (m_version >= 7)
    1256             :     {
    1257             :         // Read coordsys info.
    1258           0 :         int csclass = LEV_COORDSYS_RASTER;
    1259           0 :         /* (void) */ get(csclass, file, "csclass");
    1260             : 
    1261           0 :         if (csclass != LEV_COORDSYS_RASTER)
    1262             :         {
    1263             :             // Get projection details and units.
    1264           0 :             if (csclass == LEV_COORDSYS_LOCAL)
    1265             :             {
    1266             :                 UNITLABEL unitcode;
    1267             :                 // char szLocalUnits[8];
    1268             :                 int unitcode_int;
    1269           0 :                 if (!get(unitcode_int, file, "coordsys_units"))
    1270           0 :                     unitcode_int = UNITLABEL_M;
    1271           0 :                 unitcode = static_cast<UNITLABEL>(unitcode_int);
    1272             : 
    1273           0 :                 if (!make_local_coordsys("Leveller", unitcode))
    1274             :                 {
    1275           0 :                     CPLError(CE_Failure, CPLE_OpenFailed,
    1276             :                              "Cannot define local coordinate system.");
    1277           0 :                     return false;
    1278             :                 }
    1279             :             }
    1280           0 :             else if (csclass == LEV_COORDSYS_GEO)
    1281             :             {
    1282             :                 char szWKT[1024];
    1283           0 :                 if (!get(szWKT, 1023, file, "coordsys_wkt"))
    1284           0 :                     return false;
    1285             : 
    1286           0 :                 m_oSRS.importFromWkt(szWKT);
    1287             :             }
    1288             :             else
    1289             :             {
    1290           0 :                 CPLError(CE_Failure, CPLE_OpenFailed,
    1291             :                          "Unknown coordinate system type in %s.", pszFilename);
    1292           0 :                 return false;
    1293             :             }
    1294             : 
    1295             :             // Get ground extents.
    1296           0 :             digital_axis axis_ns, axis_ew;
    1297             : 
    1298           0 :             if (axis_ns.get(*this, file, 0) && axis_ew.get(*this, file, 1))
    1299             :             {
    1300           0 :                 m_gt[0] = axis_ew.origin(nRasterXSize);
    1301           0 :                 m_gt[1] = axis_ew.scaling(nRasterXSize);
    1302           0 :                 m_gt[2] = 0.0;
    1303             : 
    1304           0 :                 m_gt[3] = axis_ns.origin(nRasterYSize);
    1305           0 :                 m_gt[4] = 0.0;
    1306           0 :                 m_gt[5] = axis_ns.scaling(nRasterYSize);
    1307             :             }
    1308             :         }
    1309             : 
    1310             :         // Get vertical (elev) coordsys.
    1311           0 :         int bHasVertCS = FALSE;
    1312           0 :         if (get(bHasVertCS, file, "coordsys_haselevm") && bHasVertCS)
    1313             :         {
    1314           0 :             get(m_dElevScale, file, "coordsys_em_scale");
    1315           0 :             get(m_dElevBase, file, "coordsys_em_base");
    1316             :             UNITLABEL unitcode;
    1317             :             int unitcode_int;
    1318           0 :             if (get(unitcode_int, file, "coordsys_em_units"))
    1319             :             {
    1320           0 :                 unitcode = static_cast<UNITLABEL>(unitcode_int);
    1321           0 :                 const char *pszUnitID = code_to_id(unitcode);
    1322           0 :                 if (pszUnitID != nullptr)
    1323             :                 {
    1324           0 :                     strncpy(m_szElevUnits, pszUnitID, sizeof(m_szElevUnits));
    1325           0 :                     m_szElevUnits[sizeof(m_szElevUnits) - 1] = '\0';
    1326             :                 }
    1327             :                 else
    1328             :                 {
    1329           0 :                     CPLError(CE_Failure, CPLE_OpenFailed,
    1330             :                              "Unknown OEM elevation unit of measure (%d)",
    1331             :                              unitcode);
    1332           0 :                     return false;
    1333             :                 }
    1334             :             }
    1335             :             // datum and localcs are currently unused.
    1336             :         }
    1337             :     }
    1338             :     else
    1339             :     {
    1340             :         // Legacy files use world units.
    1341             :         char szWorldUnits[32];
    1342           2 :         strcpy(szWorldUnits, "m");
    1343             : 
    1344           2 :         double dWorldscale = 1.0;
    1345             : 
    1346           2 :         if (get(dWorldscale, file, "hf_worldspacing"))
    1347             :         {
    1348             :             // m_bHasWorldscale = true;
    1349           2 :             if (get(szWorldUnits, sizeof(szWorldUnits) - 1, file,
    1350             :                     "hf_worldspacinglabel"))
    1351             :             {
    1352             :                 // Drop long name, if present.
    1353           2 :                 char *p = strchr(szWorldUnits, ' ');
    1354           2 :                 if (p != nullptr)
    1355           2 :                     *p = 0;
    1356             :             }
    1357             : 
    1358             : #if 0
    1359             :           // If the units are something besides m/ft/sft,
    1360             :           // then convert them to meters.
    1361             : 
    1362             :           if(!str_equal("m", szWorldUnits)
    1363             :              && !str_equal("ft", szWorldUnits)
    1364             :              && !str_equal("sft", szWorldUnits))
    1365             :           {
    1366             :               dWorldscale = this->convert_measure(dWorldscale, szWorldUnits);
    1367             :               strcpy(szWorldUnits, "m");
    1368             :           }
    1369             : #endif
    1370             : 
    1371             :             // Our extents are such that the origin is at the
    1372             :             // center of the heightfield.
    1373           2 :             m_gt[0] = -0.5 * dWorldscale * (nRasterXSize - 1);
    1374           2 :             m_gt[3] = -0.5 * dWorldscale * (nRasterYSize - 1);
    1375           2 :             m_gt[1] = dWorldscale;
    1376           2 :             m_gt[5] = dWorldscale;
    1377             :         }
    1378           2 :         m_dElevScale = dWorldscale;  // this was 1.0 before because
    1379             :         // we were converting to real elevs ourselves, but
    1380             :         // some callers may want both the raw pixels and the
    1381             :         // transform to get real elevs.
    1382             : 
    1383           2 :         if (!make_local_coordsys("Leveller world space", szWorldUnits))
    1384             :         {
    1385           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
    1386             :                      "Cannot define local coordinate system.");
    1387           0 :             return false;
    1388             :         }
    1389             :     }
    1390             : 
    1391           2 :     return true;
    1392             : }
    1393             : 
    1394             : /************************************************************************/
    1395             : /*                          GetSpatialRef()                             */
    1396             : /************************************************************************/
    1397             : 
    1398           0 : const OGRSpatialReference *LevellerDataset::GetSpatialRef() const
    1399             : {
    1400           0 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    1401             : }
    1402             : 
    1403             : /************************************************************************/
    1404             : /*                          GetGeoTransform()                           */
    1405             : /************************************************************************/
    1406             : 
    1407           0 : CPLErr LevellerDataset::GetGeoTransform(GDALGeoTransform &gt) const
    1408             : 
    1409             : {
    1410           0 :     gt = m_gt;
    1411           0 :     return CE_None;
    1412             : }
    1413             : 
    1414             : /************************************************************************/
    1415             : /*                              Identify()                              */
    1416             : /************************************************************************/
    1417             : 
    1418       63081 : int LevellerDataset::Identify(GDALOpenInfo *poOpenInfo)
    1419             : {
    1420       63081 :     if (poOpenInfo->nHeaderBytes < 4)
    1421       56780 :         return FALSE;
    1422             : 
    1423        6301 :     return STARTS_WITH_CI(
    1424             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader), "trrn");
    1425             : }
    1426             : 
    1427             : /************************************************************************/
    1428             : /*                                Open()                                */
    1429             : /************************************************************************/
    1430             : 
    1431           2 : GDALDataset *LevellerDataset::Open(GDALOpenInfo *poOpenInfo)
    1432             : {
    1433             :     // The file should have at least 5 header bytes
    1434             :     // and hf_w, hf_b, and hf_data tags.
    1435             : 
    1436           2 :     if (poOpenInfo->nHeaderBytes < 5 + 13 + 13 + 16 ||
    1437           2 :         poOpenInfo->fpL == nullptr)
    1438           0 :         return nullptr;
    1439             : 
    1440           2 :     if (!LevellerDataset::Identify(poOpenInfo))
    1441           0 :         return nullptr;
    1442             : 
    1443           2 :     const int version = poOpenInfo->pabyHeader[4];
    1444           2 :     if (version < 4 || version > 12)
    1445           0 :         return nullptr;
    1446             : 
    1447             :     /* -------------------------------------------------------------------- */
    1448             :     /*      Create a corresponding GDALDataset.                             */
    1449             :     /* -------------------------------------------------------------------- */
    1450             : 
    1451           2 :     LevellerDataset *poDS = new LevellerDataset();
    1452             : 
    1453           2 :     poDS->m_version = version;
    1454             : 
    1455           2 :     poDS->m_fp = poOpenInfo->fpL;
    1456           2 :     poOpenInfo->fpL = nullptr;
    1457           2 :     poDS->eAccess = poOpenInfo->eAccess;
    1458             : 
    1459             :     /* -------------------------------------------------------------------- */
    1460             :     /*      Read the file.                                                  */
    1461             :     /* -------------------------------------------------------------------- */
    1462           2 :     if (!poDS->load_from_file(poDS->m_fp, poOpenInfo->pszFilename))
    1463             :     {
    1464           0 :         delete poDS;
    1465           0 :         return nullptr;
    1466             :     }
    1467             : 
    1468             :     /* -------------------------------------------------------------------- */
    1469             :     /*      Create band information objects.                                */
    1470             :     /* -------------------------------------------------------------------- */
    1471           2 :     LevellerRasterBand *poBand = new LevellerRasterBand(poDS);
    1472           2 :     poDS->SetBand(1, poBand);
    1473           2 :     if (!poBand->Init())
    1474             :     {
    1475           0 :         delete poDS;
    1476           0 :         return nullptr;
    1477             :     }
    1478             : 
    1479           2 :     poDS->SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
    1480             : 
    1481             :     /* -------------------------------------------------------------------- */
    1482             :     /*      Initialize any PAM information.                                 */
    1483             :     /* -------------------------------------------------------------------- */
    1484           2 :     poDS->SetDescription(poOpenInfo->pszFilename);
    1485           2 :     poDS->TryLoadXML();
    1486             : 
    1487             :     /* -------------------------------------------------------------------- */
    1488             :     /*      Check for external overviews.                                   */
    1489             :     /* -------------------------------------------------------------------- */
    1490           4 :     poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename,
    1491           2 :                                 poOpenInfo->GetSiblingFiles());
    1492             : 
    1493           2 :     return poDS;
    1494             : }
    1495             : 
    1496             : /************************************************************************/
    1497             : /*                        GDALRegister_Leveller()                       */
    1498             : /************************************************************************/
    1499             : 
    1500        2054 : void GDALRegister_Leveller()
    1501             : 
    1502             : {
    1503        2054 :     if (GDALGetDriverByName("Leveller") != nullptr)
    1504         283 :         return;
    1505             : 
    1506        1771 :     GDALDriver *poDriver = new GDALDriver();
    1507             : 
    1508        1771 :     poDriver->SetDescription("Leveller");
    1509        1771 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1510        1771 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "ter");
    1511        1771 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Leveller heightfield");
    1512        1771 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
    1513        1771 :                               "drivers/raster/leveller.html");
    1514        1771 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1515             : 
    1516        1771 :     poDriver->pfnIdentify = LevellerDataset::Identify;
    1517        1771 :     poDriver->pfnOpen = LevellerDataset::Open;
    1518        1771 :     poDriver->pfnCreate = LevellerDataset::Create;
    1519             : 
    1520        1771 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1521             : }

Generated by: LCOV version 1.14