Coverage Report

Created: 2025-07-23 09:13

/src/gdal/frmts/usgsdem/usgsdemdataset.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  USGS DEM Driver
4
 * Purpose:  All reader for USGS DEM Reader
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 * Portions of this module derived from the VTP USGS DEM driver by Ben
8
 * Discoe, see http://www.vterrain.org
9
 *
10
 ******************************************************************************
11
 * Copyright (c) 2001, Frank Warmerdam <warmerdam@pobox.com>
12
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
13
 *
14
 * SPDX-License-Identifier: MIT
15
 ****************************************************************************/
16
17
#include "gdal_frmts.h"
18
#include "gdal_pam.h"
19
#include "ogr_spatialref.h"
20
21
#include <algorithm>
22
#include <cmath>
23
24
typedef struct
25
{
26
    double x;
27
    double y;
28
} DPoint2;
29
30
constexpr int USGSDEM_NODATA = -32767;
31
32
GDALDataset *USGSDEMCreateCopy(const char *, GDALDataset *, int, char **,
33
                               GDALProgressFunc pfnProgress,
34
                               void *pProgressData);
35
36
/************************************************************************/
37
/*                              ReadInt()                               */
38
/************************************************************************/
39
40
static int ReadInt(VSILFILE *fp)
41
6.66k
{
42
6.66k
    char c;
43
6.66k
    int nRead = 0;
44
6.66k
    char szBuffer[12];
45
6.66k
    bool bInProlog = true;
46
47
54.9k
    while (true)
48
54.9k
    {
49
54.9k
        if (VSIFReadL(&c, 1, 1, fp) != 1)
50
416
        {
51
416
            return 0;
52
416
        }
53
54.4k
        if (bInProlog)
54
45.4k
        {
55
45.4k
            if (!isspace(static_cast<unsigned char>(c)))
56
6.26k
            {
57
6.26k
                bInProlog = false;
58
6.26k
            }
59
45.4k
        }
60
54.4k
        if (!bInProlog)
61
15.2k
        {
62
15.2k
            if (c != '-' && c != '+' && !(c >= '0' && c <= '9'))
63
6.25k
            {
64
6.25k
                CPL_IGNORE_RET_VAL(VSIFSeekL(fp, VSIFTellL(fp) - 1, SEEK_SET));
65
6.25k
                break;
66
6.25k
            }
67
9.04k
            if (nRead < 11)
68
6.77k
                szBuffer[nRead] = c;
69
9.04k
            nRead++;
70
9.04k
        }
71
54.4k
    }
72
6.25k
    szBuffer[std::min(nRead, 11)] = 0;
73
6.25k
    return atoi(szBuffer);
74
6.66k
}
75
76
typedef struct
77
{
78
    VSILFILE *fp;
79
    int max_size;
80
    char *buffer;
81
    int buffer_size;
82
    int cur_index;
83
} Buffer;
84
85
/************************************************************************/
86
/*                       USGSDEMRefillBuffer()                          */
87
/************************************************************************/
88
89
static void USGSDEMRefillBuffer(Buffer *psBuffer)
90
582
{
91
582
    memmove(psBuffer->buffer, psBuffer->buffer + psBuffer->cur_index,
92
582
            psBuffer->buffer_size - psBuffer->cur_index);
93
94
582
    psBuffer->buffer_size -= psBuffer->cur_index;
95
582
    psBuffer->buffer_size += static_cast<int>(
96
582
        VSIFReadL(psBuffer->buffer + psBuffer->buffer_size, 1,
97
582
                  psBuffer->max_size - psBuffer->buffer_size, psBuffer->fp));
98
582
    psBuffer->cur_index = 0;
99
582
}
100
101
/************************************************************************/
102
/*                      USGSDEMGetCurrentFilePos()                      */
103
/************************************************************************/
104
105
static vsi_l_offset USGSDEMGetCurrentFilePos(const Buffer *psBuffer)
106
1.46k
{
107
1.46k
    return VSIFTellL(psBuffer->fp) - psBuffer->buffer_size +
108
1.46k
           psBuffer->cur_index;
109
1.46k
}
110
111
/************************************************************************/
112
/*                      USGSDEMSetCurrentFilePos()                      */
113
/************************************************************************/
114
115
static void USGSDEMSetCurrentFilePos(Buffer *psBuffer, vsi_l_offset nNewPos)
116
1.46k
{
117
1.46k
    vsi_l_offset nCurPosFP = VSIFTellL(psBuffer->fp);
118
1.46k
    if (nNewPos >= nCurPosFP - psBuffer->buffer_size && nNewPos < nCurPosFP)
119
1.41k
    {
120
1.41k
        psBuffer->cur_index =
121
1.41k
            static_cast<int>(nNewPos - (nCurPosFP - psBuffer->buffer_size));
122
1.41k
    }
123
52
    else
124
52
    {
125
52
        CPL_IGNORE_RET_VAL(VSIFSeekL(psBuffer->fp, nNewPos, SEEK_SET));
126
52
        psBuffer->buffer_size = 0;
127
52
        psBuffer->cur_index = 0;
128
52
    }
129
1.46k
}
130
131
/************************************************************************/
132
/*               USGSDEMReadIntFromBuffer()                             */
133
/************************************************************************/
134
135
static int USGSDEMReadIntFromBuffer(Buffer *psBuffer, int *pbSuccess = nullptr)
136
179k
{
137
179k
    char c;
138
139
5.29M
    while (true)
140
5.29M
    {
141
5.29M
        if (psBuffer->cur_index >= psBuffer->buffer_size)
142
436
        {
143
436
            USGSDEMRefillBuffer(psBuffer);
144
436
            if (psBuffer->cur_index >= psBuffer->buffer_size)
145
27
            {
146
27
                if (pbSuccess)
147
27
                    *pbSuccess = FALSE;
148
27
                return 0;
149
27
            }
150
436
        }
151
152
5.29M
        c = psBuffer->buffer[psBuffer->cur_index];
153
5.29M
        psBuffer->cur_index++;
154
5.29M
        if (!isspace(static_cast<unsigned char>(c)))
155
179k
            break;
156
5.29M
    }
157
158
179k
    GIntBig nVal = 0;
159
179k
    int nSign = 1;
160
179k
    if (c == '-')
161
11.0k
        nSign = -1;
162
168k
    else if (c == '+')
163
413
        nSign = 1;
164
167k
    else if (c >= '0' && c <= '9')
165
167k
        nVal = c - '0';
166
129
    else
167
129
    {
168
129
        if (pbSuccess)
169
129
            *pbSuccess = FALSE;
170
129
        return 0;
171
129
    }
172
173
1.13M
    while (true)
174
1.13M
    {
175
1.13M
        if (psBuffer->cur_index >= psBuffer->buffer_size)
176
25
        {
177
25
            USGSDEMRefillBuffer(psBuffer);
178
25
            if (psBuffer->cur_index >= psBuffer->buffer_size)
179
5
            {
180
5
                if (pbSuccess)
181
5
                    *pbSuccess = TRUE;
182
5
                return static_cast<int>(nSign * nVal);
183
5
            }
184
25
        }
185
186
1.13M
        c = psBuffer->buffer[psBuffer->cur_index];
187
1.13M
        if (c >= '0' && c <= '9')
188
955k
        {
189
955k
            psBuffer->cur_index++;
190
955k
            if (nVal * nSign < INT_MAX && nVal * nSign > INT_MIN)
191
142k
            {
192
142k
                nVal = nVal * 10 + (c - '0');
193
142k
                if (nVal * nSign > INT_MAX)
194
217
                {
195
217
                    nVal = INT_MAX;
196
217
                    nSign = 1;
197
217
                }
198
142k
                else if (nVal * nSign < INT_MIN)
199
32
                {
200
32
                    nVal = INT_MIN;
201
32
                    nSign = 1;
202
32
                }
203
142k
            }
204
955k
        }
205
179k
        else
206
179k
        {
207
179k
            if (pbSuccess)
208
179k
                *pbSuccess = TRUE;
209
179k
            return static_cast<int>(nSign * nVal);
210
179k
        }
211
1.13M
    }
212
179k
}
213
214
/************************************************************************/
215
/*                USGSDEMReadDoubleFromBuffer()                         */
216
/************************************************************************/
217
218
static double USGSDEMReadDoubleFromBuffer(Buffer *psBuffer, int nCharCount,
219
                                          int *pbSuccess = nullptr)
220
221
136k
{
222
136k
    if (psBuffer->cur_index + nCharCount > psBuffer->buffer_size)
223
121
    {
224
121
        USGSDEMRefillBuffer(psBuffer);
225
121
        if (psBuffer->cur_index + nCharCount > psBuffer->buffer_size)
226
45
        {
227
45
            if (pbSuccess)
228
45
                *pbSuccess = FALSE;
229
45
            return 0;
230
45
        }
231
121
    }
232
233
136k
    char *szPtr = psBuffer->buffer + psBuffer->cur_index;
234
136k
    char backupC = szPtr[nCharCount];
235
136k
    szPtr[nCharCount] = 0;
236
3.42M
    for (int i = 0; i < nCharCount; i++)
237
3.28M
    {
238
3.28M
        if (szPtr[i] == 'D')
239
29.4k
            szPtr[i] = 'E';
240
3.28M
    }
241
242
136k
    double dfVal = CPLAtof(szPtr);
243
136k
    szPtr[nCharCount] = backupC;
244
136k
    psBuffer->cur_index += nCharCount;
245
246
136k
    if (pbSuccess)
247
136k
        *pbSuccess = TRUE;
248
136k
    return dfVal;
249
136k
}
250
251
/************************************************************************/
252
/*                              DConvert()                              */
253
/************************************************************************/
254
255
static double DConvert(VSILFILE *fp, int nCharCount)
256
257
5.79k
{
258
5.79k
    char szBuffer[100];
259
260
5.79k
    CPL_IGNORE_RET_VAL(VSIFReadL(szBuffer, nCharCount, 1, fp));
261
5.79k
    szBuffer[nCharCount] = '\0';
262
263
147k
    for (int i = 0; i < nCharCount; i++)
264
142k
    {
265
142k
        if (szBuffer[i] == 'D')
266
2.21k
            szBuffer[i] = 'E';
267
142k
    }
268
269
5.79k
    return CPLAtof(szBuffer);
270
5.79k
}
271
272
/************************************************************************/
273
/* ==================================================================== */
274
/*                              USGSDEMDataset                          */
275
/* ==================================================================== */
276
/************************************************************************/
277
278
class USGSDEMRasterBand;
279
280
class USGSDEMDataset final : public GDALPamDataset
281
{
282
    friend class USGSDEMRasterBand;
283
284
    int nDataStartOffset;
285
    GDALDataType eNaturalDataFormat;
286
287
    GDALGeoTransform m_gt{};
288
    OGRSpatialReference m_oSRS{};
289
290
    double fVRes;
291
292
    const char *pszUnits;
293
294
    int LoadFromFile(VSILFILE *);
295
296
    VSILFILE *fp;
297
298
  public:
299
    USGSDEMDataset();
300
    ~USGSDEMDataset();
301
302
    static int Identify(GDALOpenInfo *);
303
    static GDALDataset *Open(GDALOpenInfo *);
304
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
305
    const OGRSpatialReference *GetSpatialRef() const override;
306
};
307
308
/************************************************************************/
309
/* ==================================================================== */
310
/*                            USGSDEMRasterBand                         */
311
/* ==================================================================== */
312
/************************************************************************/
313
314
class USGSDEMRasterBand final : public GDALPamRasterBand
315
{
316
    friend class USGSDEMDataset;
317
318
  public:
319
    explicit USGSDEMRasterBand(USGSDEMDataset *);
320
321
    virtual const char *GetUnitType() override;
322
    virtual double GetNoDataValue(int *pbSuccess = nullptr) override;
323
    virtual CPLErr IReadBlock(int, int, void *) override;
324
};
325
326
/************************************************************************/
327
/*                           USGSDEMRasterBand()                            */
328
/************************************************************************/
329
330
USGSDEMRasterBand::USGSDEMRasterBand(USGSDEMDataset *poDSIn)
331
332
392
{
333
392
    this->poDS = poDSIn;
334
392
    this->nBand = 1;
335
336
392
    eDataType = poDSIn->eNaturalDataFormat;
337
338
392
    nBlockXSize = poDSIn->GetRasterXSize();
339
392
    nBlockYSize = poDSIn->GetRasterYSize();
340
392
}
341
342
/************************************************************************/
343
/*                             IReadBlock()                             */
344
/************************************************************************/
345
346
CPLErr USGSDEMRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff,
347
                                     CPL_UNUSED int nBlockYOff, void *pImage)
348
349
228
{
350
    /* int bad = FALSE; */
351
228
    USGSDEMDataset *poGDS = cpl::down_cast<USGSDEMDataset *>(poDS);
352
353
    /* -------------------------------------------------------------------- */
354
    /*      Initialize image buffer to nodata value.                        */
355
    /* -------------------------------------------------------------------- */
356
228
    GDALCopyWords(&USGSDEM_NODATA, GDT_Int32, 0, pImage, GetRasterDataType(),
357
228
                  GDALGetDataTypeSizeBytes(GetRasterDataType()),
358
228
                  GetXSize() * GetYSize());
359
360
    /* -------------------------------------------------------------------- */
361
    /*      Seek to data.                                                   */
362
    /* -------------------------------------------------------------------- */
363
228
    CPL_IGNORE_RET_VAL(VSIFSeekL(poGDS->fp, poGDS->nDataStartOffset, 0));
364
365
228
    double dfYMin = poGDS->m_gt[3] + (GetYSize() - 0.5) * poGDS->m_gt[5];
366
367
    /* -------------------------------------------------------------------- */
368
    /*      Read all the profiles into the image buffer.                    */
369
    /* -------------------------------------------------------------------- */
370
371
228
    Buffer sBuffer;
372
228
    sBuffer.max_size = 32768;
373
228
    sBuffer.buffer = static_cast<char *>(CPLMalloc(sBuffer.max_size + 1));
374
228
    sBuffer.fp = poGDS->fp;
375
228
    sBuffer.buffer_size = 0;
376
228
    sBuffer.cur_index = 0;
377
378
27.4k
    for (int i = 0; i < GetXSize(); i++)
379
27.4k
    {
380
27.4k
        int bSuccess;
381
27.4k
        const int nRowNumber = USGSDEMReadIntFromBuffer(&sBuffer, &bSuccess);
382
27.4k
        if (nRowNumber != 1)
383
2.78k
            CPLDebug("USGSDEM", "i = %d, nRowNumber = %d", i, nRowNumber);
384
27.4k
        if (bSuccess)
385
27.4k
        {
386
27.4k
            const int nColNumber =
387
27.4k
                USGSDEMReadIntFromBuffer(&sBuffer, &bSuccess);
388
27.4k
            if (nColNumber != i + 1)
389
22.8k
            {
390
22.8k
                CPLDebug("USGSDEM", "i = %d, nColNumber = %d", i, nColNumber);
391
22.8k
            }
392
27.4k
        }
393
27.4k
        const int nCPoints =
394
27.4k
            (bSuccess) ? USGSDEMReadIntFromBuffer(&sBuffer, &bSuccess) : 0;
395
#ifdef DEBUG_VERBOSE
396
        CPLDebug("USGSDEM", "i = %d, nCPoints = %d", i, nCPoints);
397
#endif
398
399
27.4k
        if (bSuccess)
400
27.4k
        {
401
27.4k
            const int nNumberOfCols =
402
27.4k
                USGSDEMReadIntFromBuffer(&sBuffer, &bSuccess);
403
27.4k
            if (nNumberOfCols != 1)
404
1.68k
            {
405
1.68k
                CPLDebug("USGSDEM", "i = %d, nNumberOfCols = %d", i,
406
1.68k
                         nNumberOfCols);
407
1.68k
            }
408
27.4k
        }
409
410
        // x-start
411
27.4k
        if (bSuccess)
412
27.3k
            /* dxStart = */ USGSDEMReadDoubleFromBuffer(&sBuffer, 24,
413
27.3k
                                                        &bSuccess);
414
415
27.4k
        double dyStart =
416
27.4k
            (bSuccess) ? USGSDEMReadDoubleFromBuffer(&sBuffer, 24, &bSuccess)
417
27.4k
                       : 0;
418
27.4k
        const double dfElevOffset =
419
27.4k
            (bSuccess) ? USGSDEMReadDoubleFromBuffer(&sBuffer, 24, &bSuccess)
420
27.4k
                       : 0;
421
422
        // min z value
423
27.4k
        if (bSuccess)
424
27.3k
            /* djunk = */ USGSDEMReadDoubleFromBuffer(&sBuffer, 24, &bSuccess);
425
426
        // max z value
427
27.4k
        if (bSuccess)
428
27.3k
            /* djunk = */ USGSDEMReadDoubleFromBuffer(&sBuffer, 24, &bSuccess);
429
27.4k
        if (!bSuccess)
430
109
        {
431
109
            CPLFree(sBuffer.buffer);
432
109
            return CE_Failure;
433
109
        }
434
435
27.3k
        if (poGDS->m_oSRS.IsGeographic())
436
21.5k
            dyStart = dyStart / 3600.0;
437
438
27.3k
        double dygap = (dfYMin - dyStart) / poGDS->m_gt[5] + 0.5;
439
27.3k
        if (dygap <= INT_MIN || dygap >= INT_MAX || !std::isfinite(dygap))
440
5
        {
441
5
            CPLFree(sBuffer.buffer);
442
5
            return CE_Failure;
443
5
        }
444
27.3k
        int lygap = static_cast<int>(dygap);
445
27.3k
        if (nCPoints <= 0)
446
518
            continue;
447
26.8k
        if (lygap > INT_MAX - nCPoints)
448
1
            lygap = INT_MAX - nCPoints;
449
26.8k
        if (lygap < 0 && GetYSize() > INT_MAX + lygap)
450
0
        {
451
0
            CPLFree(sBuffer.buffer);
452
0
            return CE_Failure;
453
0
        }
454
455
96.2k
        for (int j = lygap; j < (nCPoints + lygap); j++)
456
69.5k
        {
457
69.5k
            const int iY = GetYSize() - j - 1;
458
459
69.5k
            const int nElev = USGSDEMReadIntFromBuffer(&sBuffer, &bSuccess);
460
#ifdef DEBUG_VERBOSE
461
            CPLDebug("USGSDEM", "  j - lygap = %d, nElev = %d", j - lygap,
462
                     nElev);
463
#endif
464
465
69.5k
            if (!bSuccess)
466
92
            {
467
92
                CPLFree(sBuffer.buffer);
468
92
                return CE_Failure;
469
92
            }
470
471
69.4k
            if (iY < 0 || iY >= GetYSize())
472
27.6k
            {
473
                /* bad = TRUE; */
474
27.6k
            }
475
41.7k
            else if (nElev == USGSDEM_NODATA)
476
7.97k
                /* leave in output buffer as nodata */;
477
33.7k
            else
478
33.7k
            {
479
33.7k
                const float fComputedElev =
480
33.7k
                    static_cast<float>(nElev * poGDS->fVRes + dfElevOffset);
481
482
33.7k
                if (GetRasterDataType() == GDT_Int16)
483
32.2k
                {
484
32.2k
                    GUInt16 nVal = (fComputedElev < -32768) ? -32768
485
32.2k
                                   : (fComputedElev > 32767)
486
31.9k
                                       ? 32767
487
31.9k
                                       : static_cast<GInt16>(fComputedElev);
488
32.2k
                    reinterpret_cast<GInt16 *>(pImage)[i + iY * GetXSize()] =
489
32.2k
                        nVal;
490
32.2k
                }
491
1.46k
                else
492
1.46k
                {
493
1.46k
                    reinterpret_cast<float *>(pImage)[i + iY * GetXSize()] =
494
1.46k
                        fComputedElev;
495
1.46k
                }
496
33.7k
            }
497
69.4k
        }
498
499
26.7k
        if (poGDS->nDataStartOffset == 1024)
500
1.46k
        {
501
            // Seek to the next 1024 byte boundary.
502
            // Some files have 'junk' profile values after the valid/declared
503
            // ones
504
1.46k
            vsi_l_offset nCurPos = USGSDEMGetCurrentFilePos(&sBuffer);
505
1.46k
            vsi_l_offset nNewPos = (nCurPos + 1023) / 1024 * 1024;
506
1.46k
            if (nNewPos > nCurPos)
507
1.46k
            {
508
1.46k
                USGSDEMSetCurrentFilePos(&sBuffer, nNewPos);
509
1.46k
            }
510
1.46k
        }
511
26.7k
    }
512
22
    CPLFree(sBuffer.buffer);
513
514
22
    return CE_None;
515
228
}
516
517
/************************************************************************/
518
/*                           GetNoDataValue()                           */
519
/************************************************************************/
520
521
double USGSDEMRasterBand::GetNoDataValue(int *pbSuccess)
522
523
942
{
524
942
    if (pbSuccess != nullptr)
525
719
        *pbSuccess = TRUE;
526
527
942
    return USGSDEM_NODATA;
528
942
}
529
530
/************************************************************************/
531
/*                            GetUnitType()                             */
532
/************************************************************************/
533
const char *USGSDEMRasterBand::GetUnitType()
534
288
{
535
288
    USGSDEMDataset *poGDS = cpl::down_cast<USGSDEMDataset *>(poDS);
536
537
288
    return poGDS->pszUnits;
538
288
}
539
540
/************************************************************************/
541
/* ==================================================================== */
542
/*                              USGSDEMDataset                          */
543
/* ==================================================================== */
544
/************************************************************************/
545
546
/************************************************************************/
547
/*                           USGSDEMDataset()                           */
548
/************************************************************************/
549
550
USGSDEMDataset::USGSDEMDataset()
551
665
    : nDataStartOffset(0), eNaturalDataFormat(GDT_Unknown), fVRes(0.0),
552
665
      pszUnits(nullptr), fp(nullptr)
553
665
{
554
665
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
555
665
}
556
557
/************************************************************************/
558
/*                            ~USGSDEMDataset()                         */
559
/************************************************************************/
560
561
USGSDEMDataset::~USGSDEMDataset()
562
563
665
{
564
665
    FlushCache(true);
565
566
665
    if (fp != nullptr)
567
665
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
568
665
}
569
570
/************************************************************************/
571
/*                            LoadFromFile()                            */
572
/*                                                                      */
573
/*      If the data from DEM is in meters, then values are stored as    */
574
/*      shorts. If DEM data is in feet, then height data will be        */
575
/*      stored in float, to preserve the precision of the original      */
576
/*      data. returns true if the file was successfully opened and      */
577
/*      read.                                                           */
578
/************************************************************************/
579
580
int USGSDEMDataset::LoadFromFile(VSILFILE *InDem)
581
665
{
582
    // check for version of DEM format
583
665
    CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 864, 0));
584
585
    // Read DEM into matrix
586
665
    const int nRow = ReadInt(InDem);
587
665
    const int nColumn = ReadInt(InDem);
588
665
    const bool bNewFormat =
589
665
        VSIFTellL(InDem) >= 1024 || nRow != 1 || nColumn != 1;
590
665
    if (bNewFormat)
591
449
    {
592
449
        CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 1024, 0));  // New Format
593
449
        int i = ReadInt(InDem);
594
449
        int j = ReadInt(InDem);
595
449
        if (i != 1 || (j != 1 && j != 0))  // File OK?
596
302
        {
597
302
            CPL_IGNORE_RET_VAL(
598
302
                VSIFSeekL(InDem, 893, 0));  // Undocumented Format (39109h1.dem)
599
302
            i = ReadInt(InDem);
600
302
            j = ReadInt(InDem);
601
302
            if (i != 1 || j != 1)  // File OK?
602
216
            {
603
216
                CPL_IGNORE_RET_VAL(VSIFSeekL(
604
216
                    InDem, 918, 0));  // Latest iteration of the A record, such
605
                                      // as in fema06-140cm_2995441b.dem
606
216
                i = ReadInt(InDem);
607
216
                j = ReadInt(InDem);
608
216
                if (i != 1 || j != 1)  // File OK?
609
175
                {
610
175
                    CPLError(CE_Failure, CPLE_AppDefined,
611
175
                             "Does not appear to be a USGS DEM file.");
612
175
                    return FALSE;
613
175
                }
614
41
                else
615
41
                    nDataStartOffset = 918;
616
216
            }
617
86
            else
618
86
                nDataStartOffset = 893;
619
302
        }
620
147
        else
621
147
        {
622
147
            nDataStartOffset = 1024;
623
624
            // Some files use 1025 byte records ending with a newline character.
625
            // See https://github.com/OSGeo/gdal/issues/5007
626
147
            CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 1024, 0));
627
147
            char c;
628
147
            if (VSIFReadL(&c, 1, 1, InDem) == 1 && c == '\n' &&
629
147
                VSIFSeekL(InDem, 1024 + 1024 + 1, 0) == 0 &&
630
147
                VSIFReadL(&c, 1, 1, InDem) == 1 && c == '\n')
631
26
            {
632
26
                nDataStartOffset = 1025;
633
26
            }
634
147
        }
635
449
    }
636
216
    else
637
216
        nDataStartOffset = 864;
638
639
490
    CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 156, 0));
640
490
    const int nCoordSystem = ReadInt(InDem);
641
490
    const int iUTMZone = ReadInt(InDem);
642
643
490
    CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 528, 0));
644
490
    const int nGUnit = ReadInt(InDem);
645
490
    const int nVUnit = ReadInt(InDem);
646
647
    // Vertical Units in meters
648
490
    if (nVUnit == 1)
649
171
        pszUnits = "ft";
650
319
    else
651
319
        pszUnits = "m";
652
653
490
    CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 816, 0));
654
490
    const double dxdelta = DConvert(InDem, 12);
655
490
    const double dydelta = DConvert(InDem, 12);
656
490
    if (dydelta == 0)
657
76
        return FALSE;
658
414
    fVRes = DConvert(InDem, 12);
659
660
    /* -------------------------------------------------------------------- */
661
    /*      Should we treat this as floating point, or GInt16.              */
662
    /* -------------------------------------------------------------------- */
663
414
    if (nVUnit == 1 || fVRes < 1.0)
664
195
        eNaturalDataFormat = GDT_Float32;
665
219
    else
666
219
        eNaturalDataFormat = GDT_Int16;
667
668
    /* -------------------------------------------------------------------- */
669
    /*      Read four corner coordinates.                                   */
670
    /* -------------------------------------------------------------------- */
671
414
    CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 546, 0));
672
414
    DPoint2 corners[4];  // SW, NW, NE, SE
673
2.07k
    for (int i = 0; i < 4; i++)
674
1.65k
    {
675
1.65k
        corners[i].x = DConvert(InDem, 24);
676
1.65k
        corners[i].y = DConvert(InDem, 24);
677
1.65k
    }
678
679
    // find absolute extents of raw vales
680
414
    DPoint2 extent_min, extent_max;
681
414
    extent_min.x = std::min(corners[0].x, corners[1].x);
682
414
    extent_max.x = std::max(corners[2].x, corners[3].x);
683
414
    extent_min.y = std::min(corners[0].y, corners[3].y);
684
414
    extent_max.y = std::max(corners[1].y, corners[2].y);
685
686
    /* dElevMin = */ DConvert(InDem, 48);
687
414
    /* dElevMax = */ DConvert(InDem, 48);
688
689
414
    CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 858, 0));
690
414
    const int nProfiles = ReadInt(InDem);
691
692
    /* -------------------------------------------------------------------- */
693
    /*      Collect the spatial reference system.                           */
694
    /* -------------------------------------------------------------------- */
695
414
    OGRSpatialReference sr;
696
414
    sr.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
697
414
    bool bNAD83 = true;
698
699
    // OLD format header ends at byte 864
700
414
    if (bNewFormat)
701
200
    {
702
        // year of data compilation
703
200
        CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 876, 0));
704
200
        char szDateBuffer[5];
705
200
        CPL_IGNORE_RET_VAL(VSIFReadL(szDateBuffer, 4, 1, InDem));
706
        /* szDateBuffer[4] = 0; */
707
708
        // Horizontal datum
709
        // 1=North American Datum 1927 (NAD 27)
710
        // 2=World Geodetic System 1972 (WGS 72)
711
        // 3=WGS 84
712
        // 4=NAD 83
713
        // 5=Old Hawaii Datum
714
        // 6=Puerto Rico Datum
715
200
        CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, 890, 0));
716
717
200
        char szHorzDatum[3];
718
200
        CPL_IGNORE_RET_VAL(VSIFReadL(szHorzDatum, 1, 2, InDem));
719
200
        szHorzDatum[2] = '\0';
720
200
        const int datum = atoi(szHorzDatum);
721
200
        switch (datum)
722
200
        {
723
100
            case 1:
724
100
                sr.SetWellKnownGeogCS("NAD27");
725
100
                bNAD83 = false;
726
100
                break;
727
728
9
            case 2:
729
9
                sr.SetWellKnownGeogCS("WGS72");
730
9
                break;
731
732
3
            case 3:
733
3
                sr.SetWellKnownGeogCS("WGS84");
734
3
                break;
735
736
3
            case 4:
737
3
                sr.SetWellKnownGeogCS("NAD83");
738
3
                break;
739
740
0
            case -9:
741
0
                break;
742
743
85
            default:
744
85
                sr.SetWellKnownGeogCS("NAD27");
745
85
                break;
746
200
        }
747
200
    }
748
214
    else
749
214
    {
750
214
        sr.SetWellKnownGeogCS("NAD27");
751
214
        bNAD83 = false;
752
214
    }
753
754
414
    if (nCoordSystem == 1)  // UTM
755
202
    {
756
202
        if (iUTMZone >= -60 && iUTMZone <= 60)
757
202
        {
758
202
            sr.SetUTM(abs(iUTMZone), iUTMZone >= 0);
759
202
            if (nGUnit == 1)
760
99
            {
761
99
                sr.SetLinearUnitsAndUpdateParameters(
762
99
                    SRS_UL_US_FOOT, CPLAtof(SRS_UL_US_FOOT_CONV));
763
99
                char szUTMName[128];
764
99
                snprintf(szUTMName, sizeof(szUTMName),
765
99
                         "UTM Zone %d, Northern Hemisphere, us-ft", iUTMZone);
766
99
                sr.SetNode("PROJCS", szUTMName);
767
99
            }
768
202
        }
769
202
    }
770
212
    else if (nCoordSystem == 2)  // state plane
771
54
    {
772
54
        if (nGUnit == 1)
773
24
            sr.SetStatePlane(iUTMZone, bNAD83, "Foot",
774
24
                             CPLAtof(SRS_UL_US_FOOT_CONV));
775
30
        else
776
30
            sr.SetStatePlane(iUTMZone, bNAD83);
777
54
    }
778
779
414
    m_oSRS = std::move(sr);
780
781
    /* -------------------------------------------------------------------- */
782
    /*      For UTM we use the extents (really the UTM coordinates of       */
783
    /*      the lat/long corners of the quad) to determine the size in      */
784
    /*      pixels and lines, but we have to make the anchors be modulus    */
785
    /*      the pixel size which what really gets used.                     */
786
    /* -------------------------------------------------------------------- */
787
414
    if (nCoordSystem == 1          // UTM
788
414
        || nCoordSystem == 2       // State Plane
789
414
        || nCoordSystem == -9999)  // unknown
790
257
    {
791
        // expand extents modulus the pixel size.
792
257
        extent_min.y = floor(extent_min.y / dydelta) * dydelta;
793
257
        extent_max.y = ceil(extent_max.y / dydelta) * dydelta;
794
795
        // Forcibly compute X extents based on first profile and pixelsize.
796
257
        CPL_IGNORE_RET_VAL(VSIFSeekL(InDem, nDataStartOffset, 0));
797
257
        /* njunk = */ ReadInt(InDem);
798
257
        /* njunk = */ ReadInt(InDem);
799
257
        /* njunk = */ ReadInt(InDem);
800
257
        /* njunk = */ ReadInt(InDem);
801
257
        const double dxStart = DConvert(InDem, 24);
802
803
257
        nRasterYSize =
804
257
            static_cast<int>((extent_max.y - extent_min.y) / dydelta + 1.5);
805
257
        nRasterXSize = nProfiles;
806
807
257
        m_gt[0] = dxStart - dxdelta / 2.0;
808
257
        m_gt[1] = dxdelta;
809
257
        m_gt[2] = 0.0;
810
257
        m_gt[3] = extent_max.y + dydelta / 2.0;
811
257
        m_gt[4] = 0.0;
812
257
        m_gt[5] = -dydelta;
813
257
    }
814
    /* -------------------------------------------------------------------- */
815
    /*      Geographic -- use corners directly.                             */
816
    /* -------------------------------------------------------------------- */
817
157
    else
818
157
    {
819
157
        nRasterYSize =
820
157
            static_cast<int>((extent_max.y - extent_min.y) / dydelta + 1.5);
821
157
        nRasterXSize = nProfiles;
822
823
        // Translate extents from arc-seconds to decimal degrees.
824
157
        m_gt[0] = (extent_min.x - dxdelta / 2.0) / 3600.0;
825
157
        m_gt[1] = dxdelta / 3600.0;
826
157
        m_gt[2] = 0.0;
827
157
        m_gt[3] = (extent_max.y + dydelta / 2.0) / 3600.0;
828
157
        m_gt[4] = 0.0;
829
157
        m_gt[5] = (-dydelta) / 3600.0;
830
157
    }
831
832
    // IReadBlock() not ready for more than INT_MAX pixels, and that
833
    // would behave badly
834
414
    if (!GDALCheckDatasetDimensions(nRasterXSize, nRasterYSize) ||
835
414
        nRasterXSize > INT_MAX / nRasterYSize)
836
22
    {
837
22
        return FALSE;
838
22
    }
839
840
392
    return TRUE;
841
414
}
842
843
/************************************************************************/
844
/*                          GetGeoTransform()                           */
845
/************************************************************************/
846
847
CPLErr USGSDEMDataset::GetGeoTransform(GDALGeoTransform &gt) const
848
849
331
{
850
331
    gt = m_gt;
851
331
    return CE_None;
852
331
}
853
854
/************************************************************************/
855
/*                          GetSpatialRef()                             */
856
/************************************************************************/
857
858
const OGRSpatialReference *USGSDEMDataset::GetSpatialRef() const
859
860
395
{
861
395
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
862
395
}
863
864
/************************************************************************/
865
/*                              Identify()                              */
866
/************************************************************************/
867
868
int USGSDEMDataset::Identify(GDALOpenInfo *poOpenInfo)
869
870
549k
{
871
549k
    if (poOpenInfo->nHeaderBytes < 200)
872
494k
        return FALSE;
873
874
55.2k
    if (!STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 156, "     0") &&
875
55.2k
        !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 156, "     1") &&
876
55.2k
        !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 156, "     2") &&
877
55.2k
        !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 156, "     3") &&
878
55.2k
        !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 156, " -9999"))
879
53.7k
        return FALSE;
880
881
1.43k
    if (!STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 150, "     1") &&
882
1.43k
        !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 150, "     4"))
883
109
        return FALSE;
884
885
1.33k
    return TRUE;
886
1.43k
}
887
888
/************************************************************************/
889
/*                                Open()                                */
890
/************************************************************************/
891
892
GDALDataset *USGSDEMDataset::Open(GDALOpenInfo *poOpenInfo)
893
894
665
{
895
665
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
896
0
        return nullptr;
897
898
    /* -------------------------------------------------------------------- */
899
    /*      Create a corresponding GDALDataset.                             */
900
    /* -------------------------------------------------------------------- */
901
665
    USGSDEMDataset *poDS = new USGSDEMDataset();
902
903
665
    poDS->fp = poOpenInfo->fpL;
904
665
    poOpenInfo->fpL = nullptr;
905
906
    /* -------------------------------------------------------------------- */
907
    /*      Read the file.                                                  */
908
    /* -------------------------------------------------------------------- */
909
665
    if (!poDS->LoadFromFile(poDS->fp))
910
273
    {
911
273
        delete poDS;
912
273
        return nullptr;
913
273
    }
914
915
    /* -------------------------------------------------------------------- */
916
    /*      Confirm the requested access is supported.                      */
917
    /* -------------------------------------------------------------------- */
918
392
    if (poOpenInfo->eAccess == GA_Update)
919
0
    {
920
0
        delete poDS;
921
0
        ReportUpdateNotSupportedByDriver("USGSDEM");
922
0
        return nullptr;
923
0
    }
924
925
    /* -------------------------------------------------------------------- */
926
    /*      Create band information objects.                                */
927
    /* -------------------------------------------------------------------- */
928
392
    poDS->SetBand(1, new USGSDEMRasterBand(poDS));
929
930
392
    poDS->SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
931
932
    /* -------------------------------------------------------------------- */
933
    /*      Initialize any PAM information.                                 */
934
    /* -------------------------------------------------------------------- */
935
392
    poDS->SetDescription(poOpenInfo->pszFilename);
936
392
    poDS->TryLoadXML();
937
938
    /* -------------------------------------------------------------------- */
939
    /*      Open overviews.                                                 */
940
    /* -------------------------------------------------------------------- */
941
392
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
942
943
392
    return poDS;
944
392
}
945
946
/************************************************************************/
947
/*                        GDALRegister_USGSDEM()                        */
948
/************************************************************************/
949
950
void GDALRegister_USGSDEM()
951
952
22
{
953
22
    if (GDALGetDriverByName("USGSDEM") != nullptr)
954
0
        return;
955
956
22
    GDALDriver *poDriver = new GDALDriver();
957
958
22
    poDriver->SetDescription("USGSDEM");
959
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
960
22
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "dem");
961
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
962
22
                              "USGS Optional ASCII DEM (and CDED)");
963
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
964
22
                              "drivers/raster/usgsdem.html");
965
966
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
967
968
22
    poDriver->pfnOpen = USGSDEMDataset::Open;
969
22
    poDriver->pfnIdentify = USGSDEMDataset::Identify;
970
971
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
972
22
}