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

Generated by: LCOV version 1.14