Coverage Report

Created: 2025-08-11 09:23

/src/gdal/frmts/raw/loslasdataset.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  Horizontal Datum Formats
4
 * Purpose:  Implementation of NOAA/NADCON los/las datum shift format.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 * Financial Support: i-cubed (http://www.i-cubed.com)
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2010, Frank Warmerdam
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_string.h"
15
#include "gdal_frmts.h"
16
#include "ogr_srs_api.h"
17
#include "rawdataset.h"
18
19
#include <algorithm>
20
21
/**
22
23
NOAA .LOS/.LAS Datum Grid Shift Format
24
25
Also used for .geo file from https://geodesy.noaa.gov/GEOID/MEXICO97/
26
27
All values are little endian
28
29
Header
30
------
31
32
char[56] "NADCON EXTRACTED REGION" or
33
         "GEOID EXTRACTED REGION "
34
char[8]  "NADGRD  " or
35
         "GEOGRD  "
36
int32    grid width
37
int32    grid height
38
int32    z count (1)
39
float32  origin longitude
40
float32  grid cell width longitude
41
float32  origin latitude
42
float32  grid cell height latitude
43
float32  angle (0.0)
44
45
Data
46
----
47
48
int32   ? always 0
49
float32*gridwidth offset in arcseconds (or in metres for geoids)
50
51
Note that the record length is always gridwidth*4 + 4, and
52
even the header record is this length though it means some waste.
53
54
**/
55
56
/************************************************************************/
57
/* ==================================================================== */
58
/*                              LOSLASDataset                           */
59
/* ==================================================================== */
60
/************************************************************************/
61
62
class LOSLASDataset final : public RawDataset
63
{
64
    VSILFILE *fpImage;  // image data file.
65
66
    int nRecordLength;
67
68
    OGRSpatialReference m_oSRS{};
69
    GDALGeoTransform m_gt{};
70
71
    CPL_DISALLOW_COPY_ASSIGN(LOSLASDataset)
72
73
    CPLErr Close() override;
74
75
  public:
76
    LOSLASDataset();
77
    ~LOSLASDataset() override;
78
79
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
80
81
    const OGRSpatialReference *GetSpatialRef() const override
82
6
    {
83
6
        return &m_oSRS;
84
6
    }
85
86
    static GDALDataset *Open(GDALOpenInfo *);
87
    static int Identify(GDALOpenInfo *);
88
};
89
90
/************************************************************************/
91
/* ==================================================================== */
92
/*                              LOSLASDataset                           */
93
/* ==================================================================== */
94
/************************************************************************/
95
96
/************************************************************************/
97
/*                             LOSLASDataset()                          */
98
/************************************************************************/
99
100
16
LOSLASDataset::LOSLASDataset() : fpImage(nullptr), nRecordLength(0)
101
16
{
102
16
    m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
103
16
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
104
16
}
105
106
/************************************************************************/
107
/*                            ~LOSLASDataset()                          */
108
/************************************************************************/
109
110
LOSLASDataset::~LOSLASDataset()
111
112
16
{
113
16
    LOSLASDataset::Close();
114
16
}
115
116
/************************************************************************/
117
/*                              Close()                                 */
118
/************************************************************************/
119
120
CPLErr LOSLASDataset::Close()
121
19
{
122
19
    CPLErr eErr = CE_None;
123
19
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
124
16
    {
125
16
        if (LOSLASDataset::FlushCache(true) != CE_None)
126
0
            eErr = CE_Failure;
127
128
16
        if (fpImage)
129
16
        {
130
16
            if (VSIFCloseL(fpImage) != 0)
131
0
            {
132
0
                CPLError(CE_Failure, CPLE_FileIO, "I/O error");
133
0
                eErr = CE_Failure;
134
0
            }
135
16
        }
136
137
16
        if (GDALPamDataset::Close() != CE_None)
138
0
            eErr = CE_Failure;
139
16
    }
140
19
    return eErr;
141
19
}
142
143
/************************************************************************/
144
/*                              Identify()                              */
145
/************************************************************************/
146
147
int LOSLASDataset::Identify(GDALOpenInfo *poOpenInfo)
148
149
534k
{
150
534k
    if (poOpenInfo->nHeaderBytes < 64)
151
459k
        return FALSE;
152
153
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
154
    const char *pszExt = poOpenInfo->osExtension.c_str();
155
    if (!EQUAL(pszExt, "las") && !EQUAL(pszExt, "los") && !EQUAL(pszExt, "geo"))
156
        return FALSE;
157
#endif
158
159
74.1k
    if (!STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader) +
160
74.1k
                            56,
161
74.1k
                        "NADGRD") &&
162
74.1k
        !STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader) +
163
74.1k
                            56,
164
74.1k
                        "GEOGRD"))
165
74.1k
        return FALSE;
166
167
32
    return TRUE;
168
74.1k
}
169
170
/************************************************************************/
171
/*                                Open()                                */
172
/************************************************************************/
173
174
GDALDataset *LOSLASDataset::Open(GDALOpenInfo *poOpenInfo)
175
176
16
{
177
16
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
178
0
        return nullptr;
179
180
    /* -------------------------------------------------------------------- */
181
    /*      Confirm the requested access is supported.                      */
182
    /* -------------------------------------------------------------------- */
183
16
    if (poOpenInfo->eAccess == GA_Update)
184
0
    {
185
0
        ReportUpdateNotSupportedByDriver("LOSLAS");
186
0
        return nullptr;
187
0
    }
188
189
    /* -------------------------------------------------------------------- */
190
    /*      Create a corresponding GDALDataset.                             */
191
    /* -------------------------------------------------------------------- */
192
16
    auto poDS = std::make_unique<LOSLASDataset>();
193
16
    std::swap(poDS->fpImage, poOpenInfo->fpL);
194
195
    /* -------------------------------------------------------------------- */
196
    /*      Read the header.                                                */
197
    /* -------------------------------------------------------------------- */
198
16
    CPL_IGNORE_RET_VAL(VSIFSeekL(poDS->fpImage, 64, SEEK_SET));
199
200
16
    CPL_IGNORE_RET_VAL(VSIFReadL(&(poDS->nRasterXSize), 4, 1, poDS->fpImage));
201
16
    CPL_IGNORE_RET_VAL(VSIFReadL(&(poDS->nRasterYSize), 4, 1, poDS->fpImage));
202
203
16
    CPL_LSBPTR32(&(poDS->nRasterXSize));
204
16
    CPL_LSBPTR32(&(poDS->nRasterYSize));
205
206
16
    if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) ||
207
16
        poDS->nRasterXSize > (INT_MAX - 4) / 4)
208
13
    {
209
13
        return nullptr;
210
13
    }
211
212
3
    CPL_IGNORE_RET_VAL(VSIFSeekL(poDS->fpImage, 76, SEEK_SET));
213
214
3
    float min_lon, min_lat, delta_lon, delta_lat;
215
216
3
    CPL_IGNORE_RET_VAL(VSIFReadL(&min_lon, 4, 1, poDS->fpImage));
217
3
    CPL_IGNORE_RET_VAL(VSIFReadL(&delta_lon, 4, 1, poDS->fpImage));
218
3
    CPL_IGNORE_RET_VAL(VSIFReadL(&min_lat, 4, 1, poDS->fpImage));
219
3
    CPL_IGNORE_RET_VAL(VSIFReadL(&delta_lat, 4, 1, poDS->fpImage));
220
221
3
    CPL_LSBPTR32(&min_lon);
222
3
    CPL_LSBPTR32(&delta_lon);
223
3
    CPL_LSBPTR32(&min_lat);
224
3
    CPL_LSBPTR32(&delta_lat);
225
226
3
    poDS->nRecordLength = poDS->nRasterXSize * 4 + 4;
227
228
    /* -------------------------------------------------------------------- */
229
    /*      Create band information object.                                 */
230
    /*                                                                      */
231
    /*      Note we are setting up to read from the last image record to    */
232
    /*      the first since the data comes with the southern most record    */
233
    /*      first, not the northernmost like we would want.                 */
234
    /* -------------------------------------------------------------------- */
235
3
    auto poBand = RawRasterBand::Create(
236
3
        poDS.get(), 1, poDS->fpImage,
237
3
        static_cast<vsi_l_offset>(poDS->nRasterYSize) * poDS->nRecordLength + 4,
238
3
        4, -1 * poDS->nRecordLength, GDT_Float32,
239
3
        RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN,
240
3
        RawRasterBand::OwnFP::NO);
241
3
    if (!poBand)
242
0
        return nullptr;
243
3
    poDS->SetBand(1, std::move(poBand));
244
245
3
    if (poOpenInfo->IsExtensionEqualToCI("las"))
246
0
    {
247
0
        poDS->GetRasterBand(1)->SetDescription("Latitude Offset (arc seconds)");
248
0
    }
249
3
    else if (poOpenInfo->IsExtensionEqualToCI("los"))
250
0
    {
251
0
        poDS->GetRasterBand(1)->SetDescription(
252
0
            "Longitude Offset (arc seconds)");
253
0
        poDS->GetRasterBand(1)->SetMetadataItem("positive_value", "west");
254
0
    }
255
3
    else if (poOpenInfo->IsExtensionEqualToCI("geo"))
256
0
    {
257
0
        poDS->GetRasterBand(1)->SetDescription("Geoid undulation (meters)");
258
0
    }
259
260
    /* -------------------------------------------------------------------- */
261
    /*      Setup georeferencing.                                           */
262
    /* -------------------------------------------------------------------- */
263
3
    poDS->m_gt[0] = min_lon - delta_lon * 0.5;
264
3
    poDS->m_gt[1] = delta_lon;
265
3
    poDS->m_gt[2] = 0.0;
266
3
    poDS->m_gt[3] = min_lat + (poDS->nRasterYSize - 0.5) * delta_lat;
267
3
    poDS->m_gt[4] = 0.0;
268
3
    poDS->m_gt[5] = -1.0 * delta_lat;
269
270
    /* -------------------------------------------------------------------- */
271
    /*      Initialize any PAM information.                                 */
272
    /* -------------------------------------------------------------------- */
273
3
    poDS->SetDescription(poOpenInfo->pszFilename);
274
3
    poDS->TryLoadXML();
275
276
    /* -------------------------------------------------------------------- */
277
    /*      Check for overviews.                                            */
278
    /* -------------------------------------------------------------------- */
279
3
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
280
281
3
    return poDS.release();
282
3
}
283
284
/************************************************************************/
285
/*                          GetGeoTransform()                           */
286
/************************************************************************/
287
288
CPLErr LOSLASDataset::GetGeoTransform(GDALGeoTransform &gt) const
289
290
6
{
291
6
    gt = m_gt;
292
6
    return CE_None;
293
6
}
294
295
/************************************************************************/
296
/*                        GDALRegister_LOSLAS()                         */
297
/************************************************************************/
298
299
void GDALRegister_LOSLAS()
300
301
24
{
302
24
    if (GDALGetDriverByName("LOSLAS") != nullptr)
303
0
        return;
304
305
24
    GDALDriver *poDriver = new GDALDriver();
306
307
24
    poDriver->SetDescription("LOSLAS");
308
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
309
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
310
24
                              "NADCON .los/.las Datum Grid Shift");
311
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
312
313
24
    poDriver->pfnOpen = LOSLASDataset::Open;
314
24
    poDriver->pfnIdentify = LOSLASDataset::Identify;
315
316
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
317
24
}