Coverage Report

Created: 2026-08-14 09:29

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