LCOV - code coverage report
Current view: top level - frmts/terragen - terragendataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 275 357 77.0 %
Date: 2025-07-08 21:33:46 Functions: 31 34 91.2 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  * terragendataset.cpp,v 1.2
       3             :  *
       4             :  * Project:  Terragen(tm) TER Driver
       5             :  * Purpose:  Reader for Terragen 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             :  rcg    apr 19/06       Fixed bug with hf size being misread by one
      12             :                         if xpts/ypts tags not included in file.
      13             :                         Added Create() support.
      14             :                         Treat pixels as points.
      15             : 
      16             :  rcg    jun  6/07       Better heightscale/baseheight determination
      17             :                     when writing.
      18             : 
      19             :  *
      20             :  ******************************************************************************
      21             :  * Copyright (c) 2006-2007 Daylon Graphics Ltd.
      22             :  *
      23             :  * SPDX-License-Identifier: MIT
      24             :  ******************************************************************************
      25             :  */
      26             : 
      27             : /*
      28             :         Terragen format notes:
      29             : 
      30             :         Based on official Planetside specs.
      31             : 
      32             :         All distances along all three axes are in
      33             :         terrain units, which are 30m by default.
      34             :         If a SCAL chunk is present, however, it
      35             :         can indicate something other than 30.
      36             :         Note that uniform scaling should be used.
      37             : 
      38             :         The offset (base height) in the ALTW chunk
      39             :         is in terrain units, and the scale (height scale)
      40             :         is a normalized value using unsigned 16-bit notation.
      41             :         The physical terrain value for a read pixel is
      42             :         hv' = hv * scale / 65536 + offset.
      43             :         It still needs to be scaled by SCAL to
      44             :         get to meters.
      45             : 
      46             :         For writing:
      47             : 
      48             :         SCAL = gridpost distance in meters
      49             :         hv_px = hv_m / SCAL
      50             :         span_px = span_m / SCAL
      51             :         offset = see TerragenDataset::write_header()
      52             :         scale = see TerragenDataset::write_header()
      53             :         physical hv =
      54             :           (hv_px - offset) * 65536.0/scale
      55             : 
      56             :         We tell callers that:
      57             : 
      58             :         Elevations are Int16 when reading,
      59             :         and Float32 when writing. We need logical
      60             :         elevations when writing so that we can
      61             :         encode them with as much precision as possible
      62             :         when going down to physical 16-bit ints.
      63             :         Implementing band::SetScale/SetOffset won't work because
      64             :         it requires callers to know format write details.
      65             :         So we've added two Create() options that let the
      66             :         caller tell us the span's logical extent, and with
      67             :         those two values we can convert to physical pixels.
      68             : 
      69             :         band::GetUnitType() returns meters.
      70             :         band::GetScale() returns SCAL * (scale/65536)
      71             :         band::GetOffset() returns SCAL * offset
      72             :         ds::GetSpatialRef() returns a local CS
      73             :                 using meters.
      74             :         ds::GetGeoTransform() returns a scale matrix
      75             :                 having SCAL sx,sy members.
      76             : 
      77             :         ds::SetGeoTransform() lets us establish the
      78             :                 size of ground pixels.
      79             :         ds::_SetProjection() lets us establish what
      80             :                 units ground measures are in (also needed
      81             :                 to calc the size of ground pixels).
      82             :         band::SetUnitType() tells us what units
      83             :                 the given Float32 elevations are in.
      84             :         band::SetScale() is unused.
      85             :         band::SetOffset() is unused.
      86             : */
      87             : 
      88             : #include "cpl_string.h"
      89             : #include "gdal_frmts.h"
      90             : #include "gdal_pam.h"
      91             : #include "ogr_spatialref.h"
      92             : 
      93             : #include <cmath>
      94             : 
      95             : #include <algorithm>
      96             : 
      97             : const double kdEarthCircumPolar = 40007849;
      98             : const double kdEarthCircumEquat = 40075004;
      99             : 
     100           1 : static double average(double a, double b)
     101             : {
     102           1 :     return 0.5 * (a + b);
     103             : }
     104             : 
     105           0 : static double degrees_to_radians(double d)
     106             : {
     107           0 :     return d * (M_PI / 180);
     108             : }
     109             : 
     110           2 : static bool approx_equal(double a, double b)
     111             : {
     112           2 :     const double epsilon = 1e-5;
     113           2 :     return std::abs(a - b) <= epsilon;
     114             : }
     115             : 
     116             : /************************************************************************/
     117             : /* ==================================================================== */
     118             : /*                              TerragenDataset                         */
     119             : /* ==================================================================== */
     120             : /************************************************************************/
     121             : 
     122             : class TerragenRasterBand;
     123             : 
     124             : class TerragenDataset final : public GDALPamDataset
     125             : {
     126             :     friend class TerragenRasterBand;
     127             : 
     128             :     double m_dScale, m_dOffset,
     129             :         m_dSCAL,  // 30.0 normally, from SCAL chunk
     130             :         m_dGroundScale, m_dMetersPerGroundUnit, m_dMetersPerElevUnit,
     131             :         m_dLogSpan[2], m_span_m[2], m_span_px[2];
     132             : 
     133             :     GDALGeoTransform m_gt{};
     134             : 
     135             :     VSILFILE *m_fp;
     136             :     vsi_l_offset m_nDataOffset;
     137             : 
     138             :     GInt16 m_nHeightScale;
     139             :     GInt16 m_nBaseHeight;
     140             : 
     141             :     char *m_pszFilename;
     142             :     OGRSpatialReference m_oSRS{};
     143             :     char m_szUnits[32];
     144             : 
     145             :     bool m_bIsGeo;
     146             : 
     147             :     int LoadFromFile();
     148             : 
     149             :   public:
     150             :     TerragenDataset();
     151             :     virtual ~TerragenDataset();
     152             : 
     153             :     static GDALDataset *Open(GDALOpenInfo *);
     154             :     static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
     155             :                                int nBandsIn, GDALDataType eType,
     156             :                                char **papszOptions);
     157             : 
     158             :     virtual CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
     159             :     virtual CPLErr SetGeoTransform(const GDALGeoTransform &gt) override;
     160             :     const OGRSpatialReference *GetSpatialRef() const override;
     161             :     CPLErr SetSpatialRef(const OGRSpatialReference *poSRS) override;
     162             : 
     163             :   protected:
     164             :     bool get(GInt16 &);
     165             :     bool get(GUInt16 &);
     166             :     bool get(float &);
     167             :     bool put(GInt16);
     168             :     bool put(float);
     169             : 
     170           9 :     bool skip(size_t n)
     171             :     {
     172           9 :         return 0 == VSIFSeekL(m_fp, n, SEEK_CUR);
     173             :     }
     174             : 
     175           1 :     bool pad(size_t n)
     176             :     {
     177           1 :         return skip(n);
     178             :     }
     179             : 
     180             :     bool read_next_tag(char *);
     181             :     bool write_next_tag(const char *);
     182             :     static bool tag_is(const char *szTag, const char *);
     183             : 
     184             :     bool write_header(void);
     185             : };
     186             : 
     187             : /************************************************************************/
     188             : /* ==================================================================== */
     189             : /*                            TerragenRasterBand                        */
     190             : /* ==================================================================== */
     191             : /************************************************************************/
     192             : 
     193             : class TerragenRasterBand final : public GDALPamRasterBand
     194             : {
     195             :     friend class TerragenDataset;
     196             : 
     197             :     void *m_pvLine;
     198             :     bool m_bFirstTime;
     199             : 
     200             :   public:
     201             :     explicit TerragenRasterBand(TerragenDataset *);
     202             : 
     203          10 :     virtual ~TerragenRasterBand()
     204           5 :     {
     205           5 :         if (m_pvLine != nullptr)
     206           5 :             CPLFree(m_pvLine);
     207          10 :     }
     208             : 
     209             :     // Geomeasure support.
     210             :     virtual CPLErr IReadBlock(int, int, void *) override;
     211             :     virtual const char *GetUnitType() override;
     212             :     virtual double GetOffset(int *pbSuccess = nullptr) override;
     213             :     virtual double GetScale(int *pbSuccess = nullptr) override;
     214             : 
     215             :     virtual CPLErr IWriteBlock(int, int, void *) override;
     216             :     virtual CPLErr SetUnitType(const char *) override;
     217             : };
     218             : 
     219             : /************************************************************************/
     220             : /*                         TerragenRasterBand()                         */
     221             : /************************************************************************/
     222             : 
     223           5 : TerragenRasterBand::TerragenRasterBand(TerragenDataset *poDSIn)
     224           5 :     : m_pvLine(CPLMalloc(sizeof(GInt16) * poDSIn->GetRasterXSize())),
     225           5 :       m_bFirstTime(true)
     226             : {
     227           5 :     poDS = poDSIn;
     228           5 :     nBand = 1;
     229             : 
     230           5 :     eDataType = poDSIn->GetAccess() == GA_ReadOnly ? GDT_Int16 : GDT_Float32;
     231             : 
     232           5 :     nBlockXSize = poDSIn->GetRasterXSize();
     233           5 :     nBlockYSize = 1;
     234           5 : }
     235             : 
     236             : /************************************************************************/
     237             : /*                             IReadBlock()                             */
     238             : /************************************************************************/
     239             : 
     240          40 : CPLErr TerragenRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
     241             :                                       void *pImage)
     242             : {
     243             :     // CPLAssert( sizeof(float) == sizeof(GInt32) );
     244          40 :     CPLAssert(nBlockXOff == 0);
     245          40 :     CPLAssert(pImage != nullptr);
     246             : 
     247          40 :     TerragenDataset &ds = *reinterpret_cast<TerragenDataset *>(poDS);
     248             : 
     249             :     /* -------------------------------------------------------------------- */
     250             :     /*      Seek to scanline.
     251             :             Terragen is a bottom-top format, so we have to
     252             :             invert the row location.
     253             :      -------------------------------------------------------------------- */
     254          40 :     const size_t rowbytes = nBlockXSize * sizeof(GInt16);
     255             : 
     256          40 :     if (0 != VSIFSeekL(ds.m_fp,
     257          40 :                        ds.m_nDataOffset +
     258          40 :                            (ds.GetRasterYSize() - 1 - nBlockYOff) * rowbytes,
     259             :                        SEEK_SET))
     260             :     {
     261           0 :         CPLError(CE_Failure, CPLE_FileIO, "Terragen Seek failed:%s",
     262           0 :                  VSIStrerror(errno));
     263           0 :         return CE_Failure;
     264             :     }
     265             : 
     266             :     /* -------------------------------------------------------------------- */
     267             :     /*      Read the scanline into the line buffer.                        */
     268             :     /* -------------------------------------------------------------------- */
     269             : 
     270          40 :     if (VSIFReadL(pImage, rowbytes, 1, ds.m_fp) != 1)
     271             :     {
     272           0 :         CPLError(CE_Failure, CPLE_FileIO, "Terragen read failed:%s",
     273           0 :                  VSIStrerror(errno));
     274           0 :         return CE_Failure;
     275             :     }
     276             : 
     277             : /* -------------------------------------------------------------------- */
     278             : /*      Swap on MSB platforms.                                          */
     279             : /* -------------------------------------------------------------------- */
     280             : #ifdef CPL_MSB
     281             :     GDALSwapWords(pImage, sizeof(GInt16), nRasterXSize, sizeof(GInt16));
     282             : #endif
     283             : 
     284          40 :     return CE_None;
     285             : }
     286             : 
     287             : /************************************************************************/
     288             : /*                            GetUnitType()                             */
     289             : /************************************************************************/
     290           0 : const char *TerragenRasterBand::GetUnitType()
     291             : {
     292             :     // todo: Return elevation units.
     293             :     // For Terragen documents, it is the same as the ground units.
     294           0 :     TerragenDataset *poGDS = reinterpret_cast<TerragenDataset *>(poDS);
     295             : 
     296           0 :     return poGDS->m_szUnits;
     297             : }
     298             : 
     299             : /************************************************************************/
     300             : /*                              GetScale()                              */
     301             : /************************************************************************/
     302             : 
     303           1 : double TerragenRasterBand::GetScale(int *pbSuccess)
     304             : {
     305           1 :     const TerragenDataset &ds = *reinterpret_cast<TerragenDataset *>(poDS);
     306           1 :     if (pbSuccess != nullptr)
     307           0 :         *pbSuccess = TRUE;
     308             : 
     309           1 :     return ds.m_dScale;
     310             : }
     311             : 
     312             : /************************************************************************/
     313             : /*                             GetOffset()                              */
     314             : /************************************************************************/
     315             : 
     316           1 : double TerragenRasterBand::GetOffset(int *pbSuccess)
     317             : {
     318           1 :     const TerragenDataset &ds = *reinterpret_cast<TerragenDataset *>(poDS);
     319           1 :     if (pbSuccess != nullptr)
     320           0 :         *pbSuccess = TRUE;
     321             : 
     322           1 :     return ds.m_dOffset;
     323             : }
     324             : 
     325             : /************************************************************************/
     326             : /*                             IWriteBlock()                            */
     327             : /************************************************************************/
     328             : 
     329          20 : CPLErr TerragenRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff,
     330             :                                        int nBlockYOff, void *pImage)
     331             : {
     332          20 :     CPLAssert(nBlockXOff == 0);
     333          20 :     CPLAssert(pImage != nullptr);
     334          20 :     CPLAssert(m_pvLine != nullptr);
     335             : 
     336          20 :     const size_t pixelsize = sizeof(GInt16);
     337             : 
     338          20 :     TerragenDataset &ds = *reinterpret_cast<TerragenDataset *>(poDS);
     339          20 :     if (m_bFirstTime)
     340             :     {
     341           1 :         m_bFirstTime = false;
     342           1 :         ds.write_header();
     343           1 :         ds.m_nDataOffset = VSIFTellL(ds.m_fp);
     344             :     }
     345          20 :     const size_t rowbytes = nBlockXSize * pixelsize;
     346             : 
     347          20 :     GInt16 *pLine = reinterpret_cast<GInt16 *>(m_pvLine);
     348             : 
     349          20 :     if (0 == VSIFSeekL(ds.m_fp,
     350          20 :                        ds.m_nDataOffset +
     351             :                            // Terragen is Y inverted.
     352          20 :                            (ds.GetRasterYSize() - 1 - nBlockYOff) * rowbytes,
     353             :                        SEEK_SET))
     354             :     {
     355             :         // Convert each float32 to int16.
     356          20 :         float *pfImage = reinterpret_cast<float *>(pImage);
     357         420 :         for (size_t x = 0; x < static_cast<size_t>(nBlockXSize); x++)
     358             :         {
     359         400 :             const double f = pfImage[x] * ds.m_dMetersPerElevUnit / ds.m_dSCAL;
     360         400 :             const GInt16 hv = static_cast<GInt16>(
     361         400 :                 (f - ds.m_nBaseHeight) * 65536.0 / ds.m_nHeightScale
     362             :                 /*+ ds.m_nShift*/);
     363         400 :             pLine[x] = hv;
     364             :         }
     365             : 
     366             : #ifdef CPL_MSB
     367             :         GDALSwapWords(m_pvLine, pixelsize, nBlockXSize, pixelsize);
     368             : #endif
     369          20 :         if (1 == VSIFWriteL(m_pvLine, rowbytes, 1, ds.m_fp))
     370          20 :             return CE_None;
     371             :     }
     372             : 
     373           0 :     return CE_Failure;
     374             : }
     375             : 
     376           0 : CPLErr TerragenRasterBand::SetUnitType(const char *psz)
     377             : {
     378           0 :     TerragenDataset &ds = *reinterpret_cast<TerragenDataset *>(poDS);
     379             : 
     380           0 :     if (EQUAL(psz, "m"))
     381           0 :         ds.m_dMetersPerElevUnit = 1.0;
     382           0 :     else if (EQUAL(psz, "ft"))
     383           0 :         ds.m_dMetersPerElevUnit = 0.3048;
     384           0 :     else if (EQUAL(psz, "sft"))
     385           0 :         ds.m_dMetersPerElevUnit = 1200.0 / 3937.0;
     386             :     else
     387           0 :         return CE_Failure;
     388             : 
     389           0 :     return CE_None;
     390             : }
     391             : 
     392             : /************************************************************************/
     393             : /* ==================================================================== */
     394             : /*                          TerragenDataset                             */
     395             : /* ==================================================================== */
     396             : /************************************************************************/
     397             : 
     398             : /************************************************************************/
     399             : /*                          TerragenDataset()                           */
     400             : /************************************************************************/
     401             : 
     402          54 : TerragenDataset::TerragenDataset()
     403             :     : m_dScale(0.0), m_dOffset(0.0), m_dSCAL(30.0), m_dGroundScale(0.0),
     404             :       m_dMetersPerGroundUnit(1.0), m_dMetersPerElevUnit(1.0), m_fp(nullptr),
     405             :       m_nDataOffset(0), m_nHeightScale(0), m_nBaseHeight(0),
     406          54 :       m_pszFilename(nullptr), m_bIsGeo(false)
     407             : {
     408          54 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     409             : 
     410          54 :     m_dLogSpan[0] = 0.0;
     411          54 :     m_dLogSpan[1] = 0.0;
     412             : 
     413          54 :     m_gt[0] = 0.0;
     414          54 :     m_gt[1] = m_dSCAL;
     415          54 :     m_gt[2] = 0.0;
     416          54 :     m_gt[3] = 0.0;
     417          54 :     m_gt[4] = 0.0;
     418          54 :     m_gt[5] = m_dSCAL;
     419          54 :     m_span_m[0] = 0.0;
     420          54 :     m_span_m[1] = 0.0;
     421          54 :     m_span_px[0] = 0.0;
     422          54 :     m_span_px[1] = 0.0;
     423          54 :     memset(m_szUnits, 0, sizeof(m_szUnits));
     424          54 : }
     425             : 
     426             : /************************************************************************/
     427             : /*                          ~TerragenDataset()                          */
     428             : /************************************************************************/
     429             : 
     430         108 : TerragenDataset::~TerragenDataset()
     431             : 
     432             : {
     433          54 :     FlushCache(true);
     434             : 
     435          54 :     CPLFree(m_pszFilename);
     436             : 
     437          54 :     if (m_fp != nullptr)
     438           5 :         VSIFCloseL(m_fp);
     439         108 : }
     440             : 
     441           1 : bool TerragenDataset::write_header()
     442             : {
     443             :     char szHeader[16];
     444             :     // cppcheck-suppress bufferNotZeroTerminated
     445           1 :     memcpy(szHeader, "TERRAGENTERRAIN ", sizeof(szHeader));
     446             : 
     447           1 :     if (1 != VSIFWriteL(reinterpret_cast<void *>(szHeader), sizeof(szHeader), 1,
     448             :                         m_fp))
     449             :     {
     450           0 :         CPLError(CE_Failure, CPLE_FileIO,
     451             :                  "Couldn't write to Terragen file %s.\n"
     452             :                  "Is file system full?",
     453             :                  m_pszFilename);
     454             : 
     455           0 :         return false;
     456             :     }
     457             : 
     458             :     // --------------------------------------------------------------------
     459             :     //      Write out the heightfield dimensions, etc.
     460             :     // --------------------------------------------------------------------
     461             : 
     462           1 :     const int nXSize = GetRasterXSize();
     463           1 :     const int nYSize = GetRasterYSize();
     464             : 
     465           1 :     write_next_tag("SIZE");
     466           1 :     put(static_cast<GInt16>(std::min(nXSize, nYSize) - 1));
     467           1 :     pad(sizeof(GInt16));
     468             : 
     469           1 :     if (nXSize != nYSize)
     470             :     {
     471           0 :         write_next_tag("XPTS");
     472           0 :         put(static_cast<GInt16>(nXSize));
     473           0 :         pad(sizeof(GInt16));
     474           0 :         write_next_tag("YPTS");
     475           0 :         put(static_cast<GInt16>(nYSize));
     476           0 :         pad(sizeof(GInt16));
     477             :     }
     478             : 
     479           1 :     if (m_bIsGeo)
     480             :     {
     481             :         /*
     482             :           With a geographic projection (degrees),
     483             :           m_dGroundScale will be in degrees and
     484             :           m_dMetersPerGroundUnit is undefined.
     485             :           So we're going to estimate a m_dMetersPerGroundUnit
     486             :           value here (i.e., meters per degree).
     487             : 
     488             :           We figure out the degree size of one
     489             :           pixel, and then the latitude degrees
     490             :           of the heightfield's center. The circumference of
     491             :           the latitude's great circle lets us know how
     492             :           wide the pixel is in meters, and we
     493             :           average that with the pixel's meter breadth,
     494             :           which is based on the polar circumference.
     495             :         */
     496             : 
     497             :         /* const double m_dDegLongPerPixel =
     498             :               fabs(m_gt[1]); */
     499             : 
     500           0 :         const double m_dDegLatPerPixel = std::abs(m_gt[5]);
     501             : 
     502             :         /* const double m_dCenterLongitude =
     503             :               m_gt[0] +
     504             :               (0.5 * m_dDegLongPerPixel * (nXSize-1)); */
     505             : 
     506             :         const double m_dCenterLatitude =
     507           0 :             m_gt[3] + (0.5 * m_dDegLatPerPixel * (nYSize - 1));
     508             : 
     509             :         const double dLatCircum =
     510             :             kdEarthCircumEquat *
     511           0 :             std::sin(degrees_to_radians(90.0 - m_dCenterLatitude));
     512             : 
     513           0 :         const double dMetersPerDegLongitude = dLatCircum / 360;
     514             :         /* const double dMetersPerPixelX =
     515             :               (m_dDegLongPerPixel / 360) * dLatCircum; */
     516             : 
     517           0 :         const double dMetersPerDegLatitude = kdEarthCircumPolar / 360;
     518             :         /* const double dMetersPerPixelY =
     519             :               (m_dDegLatPerPixel / 360) * kdEarthCircumPolar; */
     520             : 
     521           0 :         m_dMetersPerGroundUnit =
     522           0 :             average(dMetersPerDegLongitude, dMetersPerDegLatitude);
     523             :     }
     524             : 
     525           1 :     m_dSCAL = m_dGroundScale * m_dMetersPerGroundUnit;
     526             : 
     527           1 :     if (m_dSCAL != 30.0)
     528             :     {
     529           1 :         const float sc = static_cast<float>(m_dSCAL);
     530           1 :         write_next_tag("SCAL");
     531           1 :         put(sc);
     532           1 :         put(sc);
     533           1 :         put(sc);
     534             :     }
     535             : 
     536           1 :     if (!write_next_tag("ALTW"))
     537             :     {
     538           0 :         CPLError(CE_Failure, CPLE_FileIO,
     539             :                  "Couldn't write to Terragen file %s.\n"
     540             :                  "Is file system full?",
     541             :                  m_pszFilename);
     542             : 
     543           0 :         return false;
     544             :     }
     545             : 
     546             :     // Compute physical scales and offsets.
     547           1 :     m_span_m[0] = m_dLogSpan[0] * m_dMetersPerElevUnit;
     548           1 :     m_span_m[1] = m_dLogSpan[1] * m_dMetersPerElevUnit;
     549             : 
     550           1 :     m_span_px[0] = m_span_m[0] / m_dSCAL;
     551           1 :     m_span_px[1] = m_span_m[1] / m_dSCAL;
     552             : 
     553           1 :     const double span_px = m_span_px[1] - m_span_px[0];
     554           1 :     m_nHeightScale = static_cast<GInt16>(span_px);
     555           1 :     if (m_nHeightScale == 0)
     556           0 :         m_nHeightScale++;
     557             : 
     558             : // TODO(schwehr): Make static functions.
     559             : #define P2L_PX(n, hs, bh) (static_cast<double>(n) / 65536.0 * (hs) + (bh))
     560             : 
     561             : #define L2P_PX(n, hs, bh) (static_cast<int>(((n) - (bh)) * 65536.0 / (hs)))
     562             : 
     563             :     // Increase the heightscale until the physical span
     564             :     // fits within a 16-bit range. The smaller the logical span,
     565             :     // the more necessary this becomes.
     566           1 :     int hs = m_nHeightScale;
     567           1 :     int bh = 0;
     568           4 :     for (; hs <= 32767; hs++)
     569             :     {
     570           4 :         double prevdelta = 1.0e30;
     571      229383 :         for (bh = -32768; bh <= 32767; bh++)
     572             :         {
     573      229380 :             const int nValley = L2P_PX(m_span_px[0], hs, bh);
     574      229380 :             if (nValley < -32768)
     575       98293 :                 continue;
     576      131087 :             const int nPeak = L2P_PX(m_span_px[1], hs, bh);
     577      131087 :             if (nPeak > 32767)
     578      131082 :                 continue;
     579             : 
     580             :             // now see how closely the baseheight gets
     581             :             // to the pixel span.
     582           5 :             const double d = P2L_PX(nValley, hs, bh);
     583           5 :             const double delta = std::abs(d - m_span_px[0]);
     584           5 :             if (delta < prevdelta)  // Converging?
     585           4 :                 prevdelta = delta;
     586             :             else
     587             :             {
     588             :                 // We're diverging, so use the previous bh
     589             :                 // and stop looking.
     590           1 :                 bh--;
     591           1 :                 break;
     592             :             }
     593             :         }
     594           4 :         if (bh != 32768)
     595           1 :             break;
     596             :     }
     597           1 :     if (hs == 32768)
     598             :     {
     599           0 :         CPLError(CE_Failure, CPLE_FileIO,
     600             :                  "Couldn't write to Terragen file %s.\n"
     601             :                  "Cannot find adequate heightscale/baseheight combination.",
     602             :                  m_pszFilename);
     603             : 
     604           0 :         return false;
     605             :     }
     606             : 
     607           1 :     m_nHeightScale = static_cast<GInt16>(hs);
     608           1 :     m_nBaseHeight = static_cast<GInt16>(bh);
     609             : 
     610             :     // m_nHeightScale is the one that gives us the
     611             :     // widest use of the 16-bit space. However, there
     612             :     // might be larger heightscales that, even though
     613             :     // the reduce the space usage, give us a better fit
     614             :     // for preserving the span extents.
     615             : 
     616           1 :     return put(m_nHeightScale) && put(m_nBaseHeight);
     617             : }
     618             : 
     619             : /************************************************************************/
     620             : /*                                get()                                 */
     621             : /************************************************************************/
     622             : 
     623           8 : bool TerragenDataset::get(GInt16 &value)
     624             : {
     625           8 :     if (1 == VSIFReadL(&value, sizeof(value), 1, m_fp))
     626             :     {
     627           8 :         CPL_LSBPTR16(&value);
     628           8 :         return true;
     629             :     }
     630           0 :     return false;
     631             : }
     632             : 
     633           4 : bool TerragenDataset::get(GUInt16 &value)
     634             : {
     635           4 :     if (1 == VSIFReadL(&value, sizeof(value), 1, m_fp))
     636             :     {
     637           4 :         CPL_LSBPTR16(&value);
     638           4 :         return true;
     639             :     }
     640           0 :     return false;
     641             : }
     642             : 
     643          12 : bool TerragenDataset::get(float &value)
     644             : {
     645          12 :     if (1 == VSIFReadL(&value, sizeof(value), 1, m_fp))
     646             :     {
     647          12 :         CPL_LSBPTR32(&value);
     648          12 :         return true;
     649             :     }
     650           0 :     return false;
     651             : }
     652             : 
     653             : /************************************************************************/
     654             : /*                                put()                                 */
     655             : /************************************************************************/
     656             : 
     657           3 : bool TerragenDataset::put(GInt16 n)
     658             : {
     659           3 :     CPL_LSBPTR16(&n);
     660           3 :     return 1 == VSIFWriteL(&n, sizeof(n), 1, m_fp);
     661             : }
     662             : 
     663           3 : bool TerragenDataset::put(float f)
     664             : {
     665           3 :     CPL_LSBPTR32(&f);
     666           3 :     return 1 == VSIFWriteL(&f, sizeof(f), 1, m_fp);
     667             : }
     668             : 
     669             : /************************************************************************/
     670             : /*                              tag stuff                               */
     671             : /************************************************************************/
     672             : 
     673          16 : bool TerragenDataset::read_next_tag(char *szTag)
     674             : {
     675          16 :     return 1 == VSIFReadL(szTag, 4, 1, m_fp);
     676             : }
     677             : 
     678           3 : bool TerragenDataset::write_next_tag(const char *szTag)
     679             : {
     680           3 :     return 1 == VSIFWriteL(reinterpret_cast<void *>(const_cast<char *>(szTag)),
     681           3 :                            4, 1, m_fp);
     682             : }
     683             : 
     684          40 : bool TerragenDataset::tag_is(const char *szTag, const char *sz)
     685             : {
     686          40 :     return 0 == memcmp(szTag, sz, 4);
     687             : }
     688             : 
     689             : /************************************************************************/
     690             : /*                            LoadFromFile()                            */
     691             : /************************************************************************/
     692             : 
     693           4 : int TerragenDataset::LoadFromFile()
     694             : {
     695           4 :     m_dSCAL = 30.0;
     696           4 :     m_nDataOffset = 0;
     697             : 
     698           4 :     if (0 != VSIFSeekL(m_fp, 16, SEEK_SET))
     699           0 :         return FALSE;
     700             : 
     701             :     char szTag[4];
     702           4 :     if (!read_next_tag(szTag) || !tag_is(szTag, "SIZE"))
     703           0 :         return FALSE;
     704             : 
     705             :     GUInt16 nSize;
     706           4 :     if (!get(nSize) || !skip(2))
     707           0 :         return FALSE;
     708             : 
     709             :     // Set dimensions to SIZE chunk. If we don't
     710             :     // encounter XPTS/YPTS chunks, we can assume
     711             :     // the terrain to be square.
     712           4 :     GUInt16 xpts = nSize + 1;
     713           4 :     GUInt16 ypts = nSize + 1;
     714             : 
     715          12 :     while (read_next_tag(szTag))
     716             :     {
     717           8 :         if (tag_is(szTag, "XPTS"))
     718             :         {
     719           0 :             get(xpts);
     720           0 :             if (xpts < nSize || !skip(2))
     721           0 :                 return FALSE;
     722           0 :             continue;
     723             :         }
     724             : 
     725           8 :         if (tag_is(szTag, "YPTS"))
     726             :         {
     727           0 :             get(ypts);
     728           0 :             if (ypts < nSize || !skip(2))
     729           0 :                 return FALSE;
     730           0 :             continue;
     731             :         }
     732             : 
     733           8 :         if (tag_is(szTag, "SCAL"))
     734             :         {
     735           4 :             float sc[3] = {0.0f};
     736           4 :             get(sc[0]);
     737           4 :             get(sc[1]);
     738           4 :             get(sc[2]);
     739           4 :             m_dSCAL = sc[1];
     740           4 :             continue;
     741             :         }
     742             : 
     743           4 :         if (tag_is(szTag, "CRAD"))
     744             :         {
     745           0 :             if (!skip(sizeof(float)))
     746           0 :                 return FALSE;
     747           0 :             continue;
     748             :         }
     749           4 :         if (tag_is(szTag, "CRVM"))
     750             :         {
     751           0 :             if (!skip(sizeof(GUInt32)))
     752           0 :                 return FALSE;
     753           0 :             continue;
     754             :         }
     755           4 :         if (tag_is(szTag, "ALTW"))
     756             :         {
     757           4 :             get(m_nHeightScale);
     758           4 :             get(m_nBaseHeight);
     759           4 :             m_nDataOffset = VSIFTellL(m_fp);
     760           4 :             if (!skip(static_cast<size_t>(xpts) * static_cast<size_t>(ypts) *
     761             :                       sizeof(GInt16)))
     762           0 :                 return FALSE;
     763           4 :             continue;
     764             :         }
     765           0 :         if (tag_is(szTag, "EOF "))
     766             :         {
     767           0 :             break;
     768             :         }
     769             :     }
     770             : 
     771           4 :     if (xpts == 0 || ypts == 0 || m_nDataOffset == 0)
     772           0 :         return FALSE;
     773             : 
     774           4 :     nRasterXSize = xpts;
     775           4 :     nRasterYSize = ypts;
     776             : 
     777             :     // todo: sanity check: do we have enough pixels?
     778             : 
     779             :     // Cache realworld scaling and offset.
     780           4 :     m_dScale = m_dSCAL / 65536 * m_nHeightScale;
     781           4 :     m_dOffset = m_dSCAL * m_nBaseHeight;
     782           4 :     strcpy(m_szUnits, "m");
     783             : 
     784             :     // Make our projection to have origin at the
     785             :     // NW corner, and groundscale to match elev scale
     786             :     // (i.e., uniform voxels).
     787           4 :     m_gt[0] = 0.0;
     788           4 :     m_gt[1] = m_dSCAL;
     789           4 :     m_gt[2] = 0.0;
     790           4 :     m_gt[3] = 0.0;
     791           4 :     m_gt[4] = 0.0;
     792           4 :     m_gt[5] = m_dSCAL;
     793             : 
     794             :     /* -------------------------------------------------------------------- */
     795             :     /*      Set projection.                                                 */
     796             :     /* -------------------------------------------------------------------- */
     797             :     // Terragen files as of Apr 2006 are partially georeferenced,
     798             :     // we can declare a local coordsys that uses meters.
     799           4 :     m_oSRS.SetLocalCS("Terragen world space");
     800           4 :     m_oSRS.SetLinearUnits("m", 1.0);
     801             : 
     802           4 :     return TRUE;
     803             : }
     804             : 
     805             : /************************************************************************/
     806             : /*                           SetSpatialRef()                            */
     807             : /************************************************************************/
     808             : 
     809           1 : CPLErr TerragenDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
     810             : {
     811             :     // Terragen files aren't really georeferenced, but
     812             :     // we should get the projection's linear units so
     813             :     // that we can scale elevations correctly.
     814             : 
     815             :     // m_dSCAL = 30.0; // default
     816             : 
     817           1 :     m_oSRS.Clear();
     818           1 :     if (poSRS)
     819           1 :         m_oSRS = *poSRS;
     820             : 
     821             :     /* -------------------------------------------------------------------- */
     822             :     /*      Linear units.                                                   */
     823             :     /* -------------------------------------------------------------------- */
     824           1 :     m_bIsGeo = poSRS != nullptr && m_oSRS.IsGeographic() != FALSE;
     825           1 :     if (m_bIsGeo)
     826             :     {
     827             :         // The caller is using degrees. We need to convert
     828             :         // to meters, otherwise we can't derive a SCAL
     829             :         // value to scale elevations with.
     830           0 :         m_bIsGeo = true;
     831             :     }
     832             :     else
     833             :     {
     834           1 :         const double dfLinear = m_oSRS.GetLinearUnits();
     835             : 
     836           1 :         if (approx_equal(dfLinear, 0.3048))
     837           0 :             m_dMetersPerGroundUnit = 0.3048;
     838           1 :         else if (approx_equal(dfLinear, CPLAtof(SRS_UL_US_FOOT_CONV)))
     839           0 :             m_dMetersPerGroundUnit = CPLAtof(SRS_UL_US_FOOT_CONV);
     840             :         else
     841           1 :             m_dMetersPerGroundUnit = 1.0;
     842             :     }
     843             : 
     844           1 :     return CE_None;
     845             : }
     846             : 
     847             : /************************************************************************/
     848             : /*                          GetSpatialRef()                             */
     849             : /************************************************************************/
     850             : 
     851           1 : const OGRSpatialReference *TerragenDataset::GetSpatialRef() const
     852             : {
     853           1 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
     854             : }
     855             : 
     856             : /************************************************************************/
     857             : /*                          SetGeoTransform()                           */
     858             : /************************************************************************/
     859             : 
     860           1 : CPLErr TerragenDataset::SetGeoTransform(const GDALGeoTransform &gt)
     861             : {
     862           1 :     m_gt = gt;
     863             : 
     864             :     // Average the projection scales.
     865           1 :     m_dGroundScale = average(fabs(m_gt[1]), fabs(m_gt[5]));
     866           1 :     return CE_None;
     867             : }
     868             : 
     869             : /************************************************************************/
     870             : /*                          GetGeoTransform()                           */
     871             : /************************************************************************/
     872             : 
     873           1 : CPLErr TerragenDataset::GetGeoTransform(GDALGeoTransform &gt) const
     874             : {
     875           1 :     gt = m_gt;
     876           1 :     return CE_None;
     877             : }
     878             : 
     879             : /************************************************************************/
     880             : /*                                Create()                                */
     881             : /************************************************************************/
     882          50 : GDALDataset *TerragenDataset::Create(const char *pszFilename, int nXSize,
     883             :                                      int nYSize, int nBandsIn,
     884             :                                      GDALDataType eType, char **papszOptions)
     885             : {
     886          50 :     TerragenDataset *poDS = new TerragenDataset();
     887             : 
     888          50 :     poDS->eAccess = GA_Update;
     889             : 
     890          50 :     poDS->m_pszFilename = CPLStrdup(pszFilename);
     891             : 
     892             :     // --------------------------------------------------------------------
     893             :     //      Verify input options.
     894             :     // --------------------------------------------------------------------
     895          50 :     const char *pszValue = CSLFetchNameValue(papszOptions, "MINUSERPIXELVALUE");
     896          50 :     if (pszValue != nullptr)
     897           1 :         poDS->m_dLogSpan[0] = CPLAtof(pszValue);
     898             : 
     899          50 :     pszValue = CSLFetchNameValue(papszOptions, "MAXUSERPIXELVALUE");
     900          50 :     if (pszValue != nullptr)
     901           1 :         poDS->m_dLogSpan[1] = CPLAtof(pszValue);
     902             : 
     903          50 :     if (poDS->m_dLogSpan[1] <= poDS->m_dLogSpan[0])
     904             :     {
     905          49 :         CPLError(CE_Failure, CPLE_AppDefined,
     906             :                  "Inverted, flat, or unspecified span for Terragen file.");
     907             : 
     908          49 :         delete poDS;
     909          49 :         return nullptr;
     910             :     }
     911             : 
     912           1 :     if (eType != GDT_Float32)
     913             :     {
     914           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     915             :                  "Attempt to create Terragen dataset with a non-float32\n"
     916             :                  "data type (%s).\n",
     917             :                  GDALGetDataTypeName(eType));
     918             : 
     919           0 :         delete poDS;
     920           0 :         return nullptr;
     921             :     }
     922             : 
     923           1 :     if (nBandsIn != 1)
     924             :     {
     925           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     926             :                  "Terragen driver doesn't support %d bands. Must be 1.\n",
     927             :                  nBandsIn);
     928             : 
     929           0 :         delete poDS;
     930           0 :         return nullptr;
     931             :     }
     932             : 
     933             :     // --------------------------------------------------------------------
     934             :     //      Try to create the file.
     935             :     // --------------------------------------------------------------------
     936             : 
     937           1 :     poDS->m_fp = VSIFOpenL(pszFilename, "wb+");
     938             : 
     939           1 :     if (poDS->m_fp == nullptr)
     940             :     {
     941           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
     942             :                  "Attempt to create file `%s' failed.\n", pszFilename);
     943           0 :         delete poDS;
     944           0 :         return nullptr;
     945             :     }
     946             : 
     947           1 :     poDS->nRasterXSize = nXSize;
     948           1 :     poDS->nRasterYSize = nYSize;
     949             : 
     950             :     // Don't bother writing the header here; the first
     951             :     // call to IWriteBlock will do that instead, since
     952             :     // the elevation data's location depends on the
     953             :     // header size.
     954             : 
     955             :     // --------------------------------------------------------------------
     956             :     //      Instance a band.
     957             :     // --------------------------------------------------------------------
     958           1 :     poDS->SetBand(1, new TerragenRasterBand(poDS));
     959             : 
     960             :     // VSIFClose( poDS->m_fp );
     961             : 
     962             :     // return (GDALDataset *) GDALOpen( pszFilename, GA_Update );
     963           1 :     return GDALDataset::FromHandle(poDS);
     964             : }
     965             : 
     966             : /************************************************************************/
     967             : /*                                Open()                                */
     968             : /************************************************************************/
     969             : 
     970       36979 : GDALDataset *TerragenDataset::Open(GDALOpenInfo *poOpenInfo)
     971             : 
     972             : {
     973             :     // The file should have at least 32 header bytes
     974       36979 :     if (poOpenInfo->nHeaderBytes < 32 || poOpenInfo->fpL == nullptr)
     975       31495 :         return nullptr;
     976             : 
     977        5484 :     if (!EQUALN(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     978             :                 "TERRAGENTERRAIN ", 16))
     979        5480 :         return nullptr;
     980             : 
     981             :     /* -------------------------------------------------------------------- */
     982             :     /*      Create a corresponding GDALDataset.                             */
     983             :     /* -------------------------------------------------------------------- */
     984           4 :     TerragenDataset *poDS = new TerragenDataset();
     985           4 :     poDS->eAccess = poOpenInfo->eAccess;
     986           4 :     poDS->m_fp = poOpenInfo->fpL;
     987           4 :     poOpenInfo->fpL = nullptr;
     988             : 
     989             :     /* -------------------------------------------------------------------- */
     990             :     /*      Read the file.                                                  */
     991             :     /* -------------------------------------------------------------------- */
     992           4 :     if (!poDS->LoadFromFile())
     993             :     {
     994           0 :         delete poDS;
     995           0 :         return nullptr;
     996             :     }
     997             : 
     998             :     /* -------------------------------------------------------------------- */
     999             :     /*      Create band information objects.                                */
    1000             :     /* -------------------------------------------------------------------- */
    1001           4 :     poDS->SetBand(1, new TerragenRasterBand(poDS));
    1002             : 
    1003           4 :     poDS->SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
    1004             : 
    1005             :     /* -------------------------------------------------------------------- */
    1006             :     /*      Initialize any PAM information.                                 */
    1007             :     /* -------------------------------------------------------------------- */
    1008           4 :     poDS->SetDescription(poOpenInfo->pszFilename);
    1009           4 :     poDS->TryLoadXML();
    1010             : 
    1011             :     /* -------------------------------------------------------------------- */
    1012             :     /*      Support overviews.                                              */
    1013             :     /* -------------------------------------------------------------------- */
    1014           4 :     poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
    1015             : 
    1016           4 :     return poDS;
    1017             : }
    1018             : 
    1019             : /************************************************************************/
    1020             : /*                        GDALRegister_Terragen()                       */
    1021             : /************************************************************************/
    1022             : 
    1023        1935 : void GDALRegister_Terragen()
    1024             : 
    1025             : {
    1026        1935 :     if (GDALGetDriverByName("Terragen") != nullptr)
    1027         282 :         return;
    1028             : 
    1029        1653 :     GDALDriver *poDriver = new GDALDriver();
    1030             : 
    1031        1653 :     poDriver->SetDescription("Terragen");
    1032        1653 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1033        1653 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "ter");
    1034        1653 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Terragen heightfield");
    1035        1653 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
    1036        1653 :                               "drivers/raster/terragen.html");
    1037             : 
    1038        1653 :     poDriver->SetMetadataItem(
    1039             :         GDAL_DMD_CREATIONOPTIONLIST,
    1040             :         "<CreationOptionList>"
    1041             :         "   <Option name='MINUSERPIXELVALUE' type='float' description='Lowest "
    1042             :         "logical elevation'/>"
    1043             :         "   <Option name='MAXUSERPIXELVALUE' type='float' description='Highest "
    1044             :         "logical elevation'/>"
    1045        1653 :         "</CreationOptionList>");
    1046        1653 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1047             : 
    1048        1653 :     poDriver->pfnOpen = TerragenDataset::Open;
    1049        1653 :     poDriver->pfnCreate = TerragenDataset::Create;
    1050             : 
    1051        1653 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1052             : }

Generated by: LCOV version 1.14