• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15885686134

25 Jun 2025 07:44PM UTC coverage: 71.084%. Remained the same
15885686134

push

github

rouault
gdal_priv.h: fix C++11 compatibility

573814 of 807237 relevant lines covered (71.08%)

250621.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.03
/frmts/terragen/terragendataset.cpp
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
static double average(double a, double b)
1✔
101
{
102
    return 0.5 * (a + b);
1✔
103
}
104

105
static double degrees_to_radians(double d)
×
106
{
107
    return d * (M_PI / 180);
×
108
}
109

110
static bool approx_equal(double a, double b)
2✔
111
{
112
    const double epsilon = 1e-5;
2✔
113
    return std::abs(a - b) <= epsilon;
2✔
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
    bool skip(size_t n)
9✔
171
    {
172
        return 0 == VSIFSeekL(m_fp, n, SEEK_CUR);
9✔
173
    }
174

175
    bool pad(size_t n)
1✔
176
    {
177
        return skip(n);
1✔
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
    virtual ~TerragenRasterBand()
10✔
204
    {
5✔
205
        if (m_pvLine != nullptr)
5✔
206
            CPLFree(m_pvLine);
5✔
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
TerragenRasterBand::TerragenRasterBand(TerragenDataset *poDSIn)
5✔
224
    : m_pvLine(CPLMalloc(sizeof(GInt16) * poDSIn->GetRasterXSize())),
5✔
225
      m_bFirstTime(true)
5✔
226
{
227
    poDS = poDSIn;
5✔
228
    nBand = 1;
5✔
229

230
    eDataType = poDSIn->GetAccess() == GA_ReadOnly ? GDT_Int16 : GDT_Float32;
5✔
231

232
    nBlockXSize = poDSIn->GetRasterXSize();
5✔
233
    nBlockYSize = 1;
5✔
234
}
5✔
235

236
/************************************************************************/
237
/*                             IReadBlock()                             */
238
/************************************************************************/
239

240
CPLErr TerragenRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
40✔
241
                                      void *pImage)
242
{
243
    // CPLAssert( sizeof(float) == sizeof(GInt32) );
244
    CPLAssert(nBlockXOff == 0);
40✔
245
    CPLAssert(pImage != nullptr);
40✔
246

247
    TerragenDataset &ds = *reinterpret_cast<TerragenDataset *>(poDS);
40✔
248

249
    /* -------------------------------------------------------------------- */
250
    /*      Seek to scanline.
251
            Terragen is a bottom-top format, so we have to
252
            invert the row location.
253
     -------------------------------------------------------------------- */
254
    const size_t rowbytes = nBlockXSize * sizeof(GInt16);
40✔
255

256
    if (0 != VSIFSeekL(ds.m_fp,
40✔
257
                       ds.m_nDataOffset +
40✔
258
                           (ds.GetRasterYSize() - 1 - nBlockYOff) * rowbytes,
40✔
259
                       SEEK_SET))
260
    {
261
        CPLError(CE_Failure, CPLE_FileIO, "Terragen Seek failed:%s",
×
262
                 VSIStrerror(errno));
×
263
        return CE_Failure;
×
264
    }
265

266
    /* -------------------------------------------------------------------- */
267
    /*      Read the scanline into the line buffer.                        */
268
    /* -------------------------------------------------------------------- */
269

270
    if (VSIFReadL(pImage, rowbytes, 1, ds.m_fp) != 1)
40✔
271
    {
272
        CPLError(CE_Failure, CPLE_FileIO, "Terragen read failed:%s",
×
273
                 VSIStrerror(errno));
×
274
        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
    return CE_None;
40✔
285
}
286

287
/************************************************************************/
288
/*                            GetUnitType()                             */
289
/************************************************************************/
290
const char *TerragenRasterBand::GetUnitType()
×
291
{
292
    // todo: Return elevation units.
293
    // For Terragen documents, it is the same as the ground units.
294
    TerragenDataset *poGDS = reinterpret_cast<TerragenDataset *>(poDS);
×
295

296
    return poGDS->m_szUnits;
×
297
}
298

299
/************************************************************************/
300
/*                              GetScale()                              */
301
/************************************************************************/
302

303
double TerragenRasterBand::GetScale(int *pbSuccess)
1✔
304
{
305
    const TerragenDataset &ds = *reinterpret_cast<TerragenDataset *>(poDS);
1✔
306
    if (pbSuccess != nullptr)
1✔
307
        *pbSuccess = TRUE;
×
308

309
    return ds.m_dScale;
1✔
310
}
311

312
/************************************************************************/
313
/*                             GetOffset()                              */
314
/************************************************************************/
315

316
double TerragenRasterBand::GetOffset(int *pbSuccess)
1✔
317
{
318
    const TerragenDataset &ds = *reinterpret_cast<TerragenDataset *>(poDS);
1✔
319
    if (pbSuccess != nullptr)
1✔
320
        *pbSuccess = TRUE;
×
321

322
    return ds.m_dOffset;
1✔
323
}
324

325
/************************************************************************/
326
/*                             IWriteBlock()                            */
327
/************************************************************************/
328

329
CPLErr TerragenRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff,
20✔
330
                                       int nBlockYOff, void *pImage)
331
{
332
    CPLAssert(nBlockXOff == 0);
20✔
333
    CPLAssert(pImage != nullptr);
20✔
334
    CPLAssert(m_pvLine != nullptr);
20✔
335

336
    const size_t pixelsize = sizeof(GInt16);
20✔
337

338
    TerragenDataset &ds = *reinterpret_cast<TerragenDataset *>(poDS);
20✔
339
    if (m_bFirstTime)
20✔
340
    {
341
        m_bFirstTime = false;
1✔
342
        ds.write_header();
1✔
343
        ds.m_nDataOffset = VSIFTellL(ds.m_fp);
1✔
344
    }
345
    const size_t rowbytes = nBlockXSize * pixelsize;
20✔
346

347
    GInt16 *pLine = reinterpret_cast<GInt16 *>(m_pvLine);
20✔
348

349
    if (0 == VSIFSeekL(ds.m_fp,
20✔
350
                       ds.m_nDataOffset +
20✔
351
                           // Terragen is Y inverted.
352
                           (ds.GetRasterYSize() - 1 - nBlockYOff) * rowbytes,
20✔
353
                       SEEK_SET))
354
    {
355
        // Convert each float32 to int16.
356
        float *pfImage = reinterpret_cast<float *>(pImage);
20✔
357
        for (size_t x = 0; x < static_cast<size_t>(nBlockXSize); x++)
420✔
358
        {
359
            const double f = pfImage[x] * ds.m_dMetersPerElevUnit / ds.m_dSCAL;
400✔
360
            const GInt16 hv = static_cast<GInt16>(
400✔
361
                (f - ds.m_nBaseHeight) * 65536.0 / ds.m_nHeightScale
400✔
362
                /*+ ds.m_nShift*/);
363
            pLine[x] = hv;
400✔
364
        }
365

366
#ifdef CPL_MSB
367
        GDALSwapWords(m_pvLine, pixelsize, nBlockXSize, pixelsize);
368
#endif
369
        if (1 == VSIFWriteL(m_pvLine, rowbytes, 1, ds.m_fp))
20✔
370
            return CE_None;
20✔
371
    }
372

373
    return CE_Failure;
×
374
}
375

376
CPLErr TerragenRasterBand::SetUnitType(const char *psz)
×
377
{
378
    TerragenDataset &ds = *reinterpret_cast<TerragenDataset *>(poDS);
×
379

380
    if (EQUAL(psz, "m"))
×
381
        ds.m_dMetersPerElevUnit = 1.0;
×
382
    else if (EQUAL(psz, "ft"))
×
383
        ds.m_dMetersPerElevUnit = 0.3048;
×
384
    else if (EQUAL(psz, "sft"))
×
385
        ds.m_dMetersPerElevUnit = 1200.0 / 3937.0;
×
386
    else
387
        return CE_Failure;
×
388

389
    return CE_None;
×
390
}
391

392
/************************************************************************/
393
/* ==================================================================== */
394
/*                          TerragenDataset                             */
395
/* ==================================================================== */
396
/************************************************************************/
397

398
/************************************************************************/
399
/*                          TerragenDataset()                           */
400
/************************************************************************/
401

402
TerragenDataset::TerragenDataset()
54✔
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
      m_pszFilename(nullptr), m_bIsGeo(false)
54✔
407
{
408
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
54✔
409

410
    m_dLogSpan[0] = 0.0;
54✔
411
    m_dLogSpan[1] = 0.0;
54✔
412

413
    m_gt[0] = 0.0;
54✔
414
    m_gt[1] = m_dSCAL;
54✔
415
    m_gt[2] = 0.0;
54✔
416
    m_gt[3] = 0.0;
54✔
417
    m_gt[4] = 0.0;
54✔
418
    m_gt[5] = m_dSCAL;
54✔
419
    m_span_m[0] = 0.0;
54✔
420
    m_span_m[1] = 0.0;
54✔
421
    m_span_px[0] = 0.0;
54✔
422
    m_span_px[1] = 0.0;
54✔
423
    memset(m_szUnits, 0, sizeof(m_szUnits));
54✔
424
}
54✔
425

426
/************************************************************************/
427
/*                          ~TerragenDataset()                          */
428
/************************************************************************/
429

430
TerragenDataset::~TerragenDataset()
108✔
431

432
{
433
    FlushCache(true);
54✔
434

435
    CPLFree(m_pszFilename);
54✔
436

437
    if (m_fp != nullptr)
54✔
438
        VSIFCloseL(m_fp);
5✔
439
}
108✔
440

441
bool TerragenDataset::write_header()
1✔
442
{
443
    char szHeader[16];
444
    // cppcheck-suppress bufferNotZeroTerminated
445
    memcpy(szHeader, "TERRAGENTERRAIN ", sizeof(szHeader));
1✔
446

447
    if (1 != VSIFWriteL(reinterpret_cast<void *>(szHeader), sizeof(szHeader), 1,
1✔
448
                        m_fp))
449
    {
450
        CPLError(CE_Failure, CPLE_FileIO,
×
451
                 "Couldn't write to Terragen file %s.\n"
452
                 "Is file system full?",
453
                 m_pszFilename);
454

455
        return false;
×
456
    }
457

458
    // --------------------------------------------------------------------
459
    //      Write out the heightfield dimensions, etc.
460
    // --------------------------------------------------------------------
461

462
    const int nXSize = GetRasterXSize();
1✔
463
    const int nYSize = GetRasterYSize();
1✔
464

465
    write_next_tag("SIZE");
1✔
466
    put(static_cast<GInt16>(std::min(nXSize, nYSize) - 1));
1✔
467
    pad(sizeof(GInt16));
1✔
468

469
    if (nXSize != nYSize)
1✔
470
    {
471
        write_next_tag("XPTS");
×
472
        put(static_cast<GInt16>(nXSize));
×
473
        pad(sizeof(GInt16));
×
474
        write_next_tag("YPTS");
×
475
        put(static_cast<GInt16>(nYSize));
×
476
        pad(sizeof(GInt16));
×
477
    }
478

479
    if (m_bIsGeo)
1✔
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
        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
            m_gt[3] + (0.5 * m_dDegLatPerPixel * (nYSize - 1));
×
508

509
        const double dLatCircum =
510
            kdEarthCircumEquat *
511
            std::sin(degrees_to_radians(90.0 - m_dCenterLatitude));
×
512

513
        const double dMetersPerDegLongitude = dLatCircum / 360;
×
514
        /* const double dMetersPerPixelX =
515
              (m_dDegLongPerPixel / 360) * dLatCircum; */
516

517
        const double dMetersPerDegLatitude = kdEarthCircumPolar / 360;
×
518
        /* const double dMetersPerPixelY =
519
              (m_dDegLatPerPixel / 360) * kdEarthCircumPolar; */
520

521
        m_dMetersPerGroundUnit =
×
522
            average(dMetersPerDegLongitude, dMetersPerDegLatitude);
×
523
    }
524

525
    m_dSCAL = m_dGroundScale * m_dMetersPerGroundUnit;
1✔
526

527
    if (m_dSCAL != 30.0)
1✔
528
    {
529
        const float sc = static_cast<float>(m_dSCAL);
1✔
530
        write_next_tag("SCAL");
1✔
531
        put(sc);
1✔
532
        put(sc);
1✔
533
        put(sc);
1✔
534
    }
535

536
    if (!write_next_tag("ALTW"))
1✔
537
    {
538
        CPLError(CE_Failure, CPLE_FileIO,
×
539
                 "Couldn't write to Terragen file %s.\n"
540
                 "Is file system full?",
541
                 m_pszFilename);
542

543
        return false;
×
544
    }
545

546
    // Compute physical scales and offsets.
547
    m_span_m[0] = m_dLogSpan[0] * m_dMetersPerElevUnit;
1✔
548
    m_span_m[1] = m_dLogSpan[1] * m_dMetersPerElevUnit;
1✔
549

550
    m_span_px[0] = m_span_m[0] / m_dSCAL;
1✔
551
    m_span_px[1] = m_span_m[1] / m_dSCAL;
1✔
552

553
    const double span_px = m_span_px[1] - m_span_px[0];
1✔
554
    m_nHeightScale = static_cast<GInt16>(span_px);
1✔
555
    if (m_nHeightScale == 0)
1✔
556
        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
    int hs = m_nHeightScale;
1✔
567
    int bh = 0;
1✔
568
    for (; hs <= 32767; hs++)
4✔
569
    {
570
        double prevdelta = 1.0e30;
4✔
571
        for (bh = -32768; bh <= 32767; bh++)
229,383✔
572
        {
573
            const int nValley = L2P_PX(m_span_px[0], hs, bh);
229,380✔
574
            if (nValley < -32768)
229,380✔
575
                continue;
98,293✔
576
            const int nPeak = L2P_PX(m_span_px[1], hs, bh);
131,087✔
577
            if (nPeak > 32767)
131,087✔
578
                continue;
131,082✔
579

580
            // now see how closely the baseheight gets
581
            // to the pixel span.
582
            const double d = P2L_PX(nValley, hs, bh);
5✔
583
            const double delta = std::abs(d - m_span_px[0]);
5✔
584
            if (delta < prevdelta)  // Converging?
5✔
585
                prevdelta = delta;
4✔
586
            else
587
            {
588
                // We're diverging, so use the previous bh
589
                // and stop looking.
590
                bh--;
1✔
591
                break;
1✔
592
            }
593
        }
594
        if (bh != 32768)
4✔
595
            break;
1✔
596
    }
597
    if (hs == 32768)
1✔
598
    {
599
        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
        return false;
×
605
    }
606

607
    m_nHeightScale = static_cast<GInt16>(hs);
1✔
608
    m_nBaseHeight = static_cast<GInt16>(bh);
1✔
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
    return put(m_nHeightScale) && put(m_nBaseHeight);
1✔
617
}
618

619
/************************************************************************/
620
/*                                get()                                 */
621
/************************************************************************/
622

623
bool TerragenDataset::get(GInt16 &value)
8✔
624
{
625
    if (1 == VSIFReadL(&value, sizeof(value), 1, m_fp))
8✔
626
    {
627
        CPL_LSBPTR16(&value);
8✔
628
        return true;
8✔
629
    }
630
    return false;
×
631
}
632

633
bool TerragenDataset::get(GUInt16 &value)
4✔
634
{
635
    if (1 == VSIFReadL(&value, sizeof(value), 1, m_fp))
4✔
636
    {
637
        CPL_LSBPTR16(&value);
4✔
638
        return true;
4✔
639
    }
640
    return false;
×
641
}
642

643
bool TerragenDataset::get(float &value)
12✔
644
{
645
    if (1 == VSIFReadL(&value, sizeof(value), 1, m_fp))
12✔
646
    {
647
        CPL_LSBPTR32(&value);
12✔
648
        return true;
12✔
649
    }
650
    return false;
×
651
}
652

653
/************************************************************************/
654
/*                                put()                                 */
655
/************************************************************************/
656

657
bool TerragenDataset::put(GInt16 n)
3✔
658
{
659
    CPL_LSBPTR16(&n);
3✔
660
    return 1 == VSIFWriteL(&n, sizeof(n), 1, m_fp);
3✔
661
}
662

663
bool TerragenDataset::put(float f)
3✔
664
{
665
    CPL_LSBPTR32(&f);
3✔
666
    return 1 == VSIFWriteL(&f, sizeof(f), 1, m_fp);
3✔
667
}
668

669
/************************************************************************/
670
/*                              tag stuff                               */
671
/************************************************************************/
672

673
bool TerragenDataset::read_next_tag(char *szTag)
16✔
674
{
675
    return 1 == VSIFReadL(szTag, 4, 1, m_fp);
16✔
676
}
677

678
bool TerragenDataset::write_next_tag(const char *szTag)
3✔
679
{
680
    return 1 == VSIFWriteL(reinterpret_cast<void *>(const_cast<char *>(szTag)),
3✔
681
                           4, 1, m_fp);
3✔
682
}
683

684
bool TerragenDataset::tag_is(const char *szTag, const char *sz)
40✔
685
{
686
    return 0 == memcmp(szTag, sz, 4);
40✔
687
}
688

689
/************************************************************************/
690
/*                            LoadFromFile()                            */
691
/************************************************************************/
692

693
int TerragenDataset::LoadFromFile()
4✔
694
{
695
    m_dSCAL = 30.0;
4✔
696
    m_nDataOffset = 0;
4✔
697

698
    if (0 != VSIFSeekL(m_fp, 16, SEEK_SET))
4✔
699
        return FALSE;
×
700

701
    char szTag[4];
702
    if (!read_next_tag(szTag) || !tag_is(szTag, "SIZE"))
4✔
703
        return FALSE;
×
704

705
    GUInt16 nSize;
706
    if (!get(nSize) || !skip(2))
4✔
707
        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
    GUInt16 xpts = nSize + 1;
4✔
713
    GUInt16 ypts = nSize + 1;
4✔
714

715
    while (read_next_tag(szTag))
12✔
716
    {
717
        if (tag_is(szTag, "XPTS"))
8✔
718
        {
719
            get(xpts);
×
720
            if (xpts < nSize || !skip(2))
×
721
                return FALSE;
×
722
            continue;
×
723
        }
724

725
        if (tag_is(szTag, "YPTS"))
8✔
726
        {
727
            get(ypts);
×
728
            if (ypts < nSize || !skip(2))
×
729
                return FALSE;
×
730
            continue;
×
731
        }
732

733
        if (tag_is(szTag, "SCAL"))
8✔
734
        {
735
            float sc[3] = {0.0f};
4✔
736
            get(sc[0]);
4✔
737
            get(sc[1]);
4✔
738
            get(sc[2]);
4✔
739
            m_dSCAL = sc[1];
4✔
740
            continue;
4✔
741
        }
742

743
        if (tag_is(szTag, "CRAD"))
4✔
744
        {
745
            if (!skip(sizeof(float)))
×
746
                return FALSE;
×
747
            continue;
×
748
        }
749
        if (tag_is(szTag, "CRVM"))
4✔
750
        {
751
            if (!skip(sizeof(GUInt32)))
×
752
                return FALSE;
×
753
            continue;
×
754
        }
755
        if (tag_is(szTag, "ALTW"))
4✔
756
        {
757
            get(m_nHeightScale);
4✔
758
            get(m_nBaseHeight);
4✔
759
            m_nDataOffset = VSIFTellL(m_fp);
4✔
760
            if (!skip(static_cast<size_t>(xpts) * static_cast<size_t>(ypts) *
4✔
761
                      sizeof(GInt16)))
762
                return FALSE;
×
763
            continue;
4✔
764
        }
765
        if (tag_is(szTag, "EOF "))
×
766
        {
767
            break;
×
768
        }
769
    }
770

771
    if (xpts == 0 || ypts == 0 || m_nDataOffset == 0)
4✔
772
        return FALSE;
×
773

774
    nRasterXSize = xpts;
4✔
775
    nRasterYSize = ypts;
4✔
776

777
    // todo: sanity check: do we have enough pixels?
778

779
    // Cache realworld scaling and offset.
780
    m_dScale = m_dSCAL / 65536 * m_nHeightScale;
4✔
781
    m_dOffset = m_dSCAL * m_nBaseHeight;
4✔
782
    strcpy(m_szUnits, "m");
4✔
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
    m_gt[0] = 0.0;
4✔
788
    m_gt[1] = m_dSCAL;
4✔
789
    m_gt[2] = 0.0;
4✔
790
    m_gt[3] = 0.0;
4✔
791
    m_gt[4] = 0.0;
4✔
792
    m_gt[5] = m_dSCAL;
4✔
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
    m_oSRS.SetLocalCS("Terragen world space");
4✔
800
    m_oSRS.SetLinearUnits("m", 1.0);
4✔
801

802
    return TRUE;
4✔
803
}
804

805
/************************************************************************/
806
/*                           SetSpatialRef()                            */
807
/************************************************************************/
808

809
CPLErr TerragenDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
1✔
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
    m_oSRS.Clear();
1✔
818
    if (poSRS)
1✔
819
        m_oSRS = *poSRS;
1✔
820

821
    /* -------------------------------------------------------------------- */
822
    /*      Linear units.                                                   */
823
    /* -------------------------------------------------------------------- */
824
    m_bIsGeo = poSRS != nullptr && m_oSRS.IsGeographic() != FALSE;
1✔
825
    if (m_bIsGeo)
1✔
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
        m_bIsGeo = true;
×
831
    }
832
    else
833
    {
834
        const double dfLinear = m_oSRS.GetLinearUnits();
1✔
835

836
        if (approx_equal(dfLinear, 0.3048))
1✔
837
            m_dMetersPerGroundUnit = 0.3048;
×
838
        else if (approx_equal(dfLinear, CPLAtof(SRS_UL_US_FOOT_CONV)))
1✔
839
            m_dMetersPerGroundUnit = CPLAtof(SRS_UL_US_FOOT_CONV);
×
840
        else
841
            m_dMetersPerGroundUnit = 1.0;
1✔
842
    }
843

844
    return CE_None;
1✔
845
}
846

847
/************************************************************************/
848
/*                          GetSpatialRef()                             */
849
/************************************************************************/
850

851
const OGRSpatialReference *TerragenDataset::GetSpatialRef() const
1✔
852
{
853
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
1✔
854
}
855

856
/************************************************************************/
857
/*                          SetGeoTransform()                           */
858
/************************************************************************/
859

860
CPLErr TerragenDataset::SetGeoTransform(const GDALGeoTransform &gt)
1✔
861
{
862
    m_gt = gt;
1✔
863

864
    // Average the projection scales.
865
    m_dGroundScale = average(fabs(m_gt[1]), fabs(m_gt[5]));
1✔
866
    return CE_None;
1✔
867
}
868

869
/************************************************************************/
870
/*                          GetGeoTransform()                           */
871
/************************************************************************/
872

873
CPLErr TerragenDataset::GetGeoTransform(GDALGeoTransform &gt) const
1✔
874
{
875
    gt = m_gt;
1✔
876
    return CE_None;
1✔
877
}
878

879
/************************************************************************/
880
/*                                Create()                                */
881
/************************************************************************/
882
GDALDataset *TerragenDataset::Create(const char *pszFilename, int nXSize,
50✔
883
                                     int nYSize, int nBandsIn,
884
                                     GDALDataType eType, char **papszOptions)
885
{
886
    TerragenDataset *poDS = new TerragenDataset();
50✔
887

888
    poDS->eAccess = GA_Update;
50✔
889

890
    poDS->m_pszFilename = CPLStrdup(pszFilename);
50✔
891

892
    // --------------------------------------------------------------------
893
    //      Verify input options.
894
    // --------------------------------------------------------------------
895
    const char *pszValue = CSLFetchNameValue(papszOptions, "MINUSERPIXELVALUE");
50✔
896
    if (pszValue != nullptr)
50✔
897
        poDS->m_dLogSpan[0] = CPLAtof(pszValue);
1✔
898

899
    pszValue = CSLFetchNameValue(papszOptions, "MAXUSERPIXELVALUE");
50✔
900
    if (pszValue != nullptr)
50✔
901
        poDS->m_dLogSpan[1] = CPLAtof(pszValue);
1✔
902

903
    if (poDS->m_dLogSpan[1] <= poDS->m_dLogSpan[0])
50✔
904
    {
905
        CPLError(CE_Failure, CPLE_AppDefined,
49✔
906
                 "Inverted, flat, or unspecified span for Terragen file.");
907

908
        delete poDS;
49✔
909
        return nullptr;
49✔
910
    }
911

912
    if (eType != GDT_Float32)
1✔
913
    {
914
        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
        delete poDS;
×
920
        return nullptr;
×
921
    }
922

923
    if (nBandsIn != 1)
1✔
924
    {
925
        CPLError(CE_Failure, CPLE_NotSupported,
×
926
                 "Terragen driver doesn't support %d bands. Must be 1.\n",
927
                 nBandsIn);
928

929
        delete poDS;
×
930
        return nullptr;
×
931
    }
932

933
    // --------------------------------------------------------------------
934
    //      Try to create the file.
935
    // --------------------------------------------------------------------
936

937
    poDS->m_fp = VSIFOpenL(pszFilename, "wb+");
1✔
938

939
    if (poDS->m_fp == nullptr)
1✔
940
    {
941
        CPLError(CE_Failure, CPLE_OpenFailed,
×
942
                 "Attempt to create file `%s' failed.\n", pszFilename);
943
        delete poDS;
×
944
        return nullptr;
×
945
    }
946

947
    poDS->nRasterXSize = nXSize;
1✔
948
    poDS->nRasterYSize = nYSize;
1✔
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
    poDS->SetBand(1, new TerragenRasterBand(poDS));
1✔
959

960
    // VSIFClose( poDS->m_fp );
961

962
    // return (GDALDataset *) GDALOpen( pszFilename, GA_Update );
963
    return GDALDataset::FromHandle(poDS);
1✔
964
}
965

966
/************************************************************************/
967
/*                                Open()                                */
968
/************************************************************************/
969

970
GDALDataset *TerragenDataset::Open(GDALOpenInfo *poOpenInfo)
36,702✔
971

972
{
973
    // The file should have at least 32 header bytes
974
    if (poOpenInfo->nHeaderBytes < 32 || poOpenInfo->fpL == nullptr)
36,702✔
975
        return nullptr;
31,238✔
976

977
    if (!EQUALN(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
5,464✔
978
                "TERRAGENTERRAIN ", 16))
979
        return nullptr;
5,460✔
980

981
    /* -------------------------------------------------------------------- */
982
    /*      Create a corresponding GDALDataset.                             */
983
    /* -------------------------------------------------------------------- */
984
    TerragenDataset *poDS = new TerragenDataset();
4✔
985
    poDS->eAccess = poOpenInfo->eAccess;
4✔
986
    poDS->m_fp = poOpenInfo->fpL;
4✔
987
    poOpenInfo->fpL = nullptr;
4✔
988

989
    /* -------------------------------------------------------------------- */
990
    /*      Read the file.                                                  */
991
    /* -------------------------------------------------------------------- */
992
    if (!poDS->LoadFromFile())
4✔
993
    {
994
        delete poDS;
×
995
        return nullptr;
×
996
    }
997

998
    /* -------------------------------------------------------------------- */
999
    /*      Create band information objects.                                */
1000
    /* -------------------------------------------------------------------- */
1001
    poDS->SetBand(1, new TerragenRasterBand(poDS));
4✔
1002

1003
    poDS->SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
4✔
1004

1005
    /* -------------------------------------------------------------------- */
1006
    /*      Initialize any PAM information.                                 */
1007
    /* -------------------------------------------------------------------- */
1008
    poDS->SetDescription(poOpenInfo->pszFilename);
4✔
1009
    poDS->TryLoadXML();
4✔
1010

1011
    /* -------------------------------------------------------------------- */
1012
    /*      Support overviews.                                              */
1013
    /* -------------------------------------------------------------------- */
1014
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
4✔
1015

1016
    return poDS;
4✔
1017
}
1018

1019
/************************************************************************/
1020
/*                        GDALRegister_Terragen()                       */
1021
/************************************************************************/
1022

1023
void GDALRegister_Terragen()
1,911✔
1024

1025
{
1026
    if (GDALGetDriverByName("Terragen") != nullptr)
1,911✔
1027
        return;
282✔
1028

1029
    GDALDriver *poDriver = new GDALDriver();
1,629✔
1030

1031
    poDriver->SetDescription("Terragen");
1,629✔
1032
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1,629✔
1033
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "ter");
1,629✔
1034
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Terragen heightfield");
1,629✔
1035
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
1,629✔
1036
                              "drivers/raster/terragen.html");
1,629✔
1037

1038
    poDriver->SetMetadataItem(
1,629✔
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
        "</CreationOptionList>");
1,629✔
1046
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1,629✔
1047

1048
    poDriver->pfnOpen = TerragenDataset::Open;
1,629✔
1049
    poDriver->pfnCreate = TerragenDataset::Create;
1,629✔
1050

1051
    GetGDALDriverManager()->RegisterDriver(poDriver);
1,629✔
1052
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc