Coverage Report

Created: 2025-06-09 07:02

/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
    double adfGeoTransform[6];
70
71
    CPL_DISALLOW_COPY_ASSIGN(LOSLASDataset)
72
73
    CPLErr Close() override;
74
75
  public:
76
    LOSLASDataset();
77
    ~LOSLASDataset() override;
78
79
    CPLErr GetGeoTransform(double *padfTransform) override;
80
81
    const OGRSpatialReference *GetSpatialRef() const override
82
8
    {
83
8
        return &m_oSRS;
84
8
    }
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
14
LOSLASDataset::LOSLASDataset() : fpImage(nullptr), nRecordLength(0)
101
14
{
102
14
    m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
103
14
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
104
105
14
    memset(adfGeoTransform, 0, sizeof(adfGeoTransform));
106
14
}
107
108
/************************************************************************/
109
/*                            ~LOSLASDataset()                          */
110
/************************************************************************/
111
112
LOSLASDataset::~LOSLASDataset()
113
114
14
{
115
14
    LOSLASDataset::Close();
116
14
}
117
118
/************************************************************************/
119
/*                              Close()                                 */
120
/************************************************************************/
121
122
CPLErr LOSLASDataset::Close()
123
20
{
124
20
    CPLErr eErr = CE_None;
125
20
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
126
14
    {
127
14
        if (LOSLASDataset::FlushCache(true) != CE_None)
128
0
            eErr = CE_Failure;
129
130
14
        if (fpImage)
131
14
        {
132
14
            if (VSIFCloseL(fpImage) != 0)
133
0
            {
134
0
                CPLError(CE_Failure, CPLE_FileIO, "I/O error");
135
0
                eErr = CE_Failure;
136
0
            }
137
14
        }
138
139
14
        if (GDALPamDataset::Close() != CE_None)
140
0
            eErr = CE_Failure;
141
14
    }
142
20
    return eErr;
143
20
}
144
145
/************************************************************************/
146
/*                              Identify()                              */
147
/************************************************************************/
148
149
int LOSLASDataset::Identify(GDALOpenInfo *poOpenInfo)
150
151
39.4k
{
152
39.4k
    if (poOpenInfo->nHeaderBytes < 64)
153
25.7k
        return FALSE;
154
155
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
156
    const char *pszExt = poOpenInfo->osExtension.c_str();
157
    if (!EQUAL(pszExt, "las") && !EQUAL(pszExt, "los") && !EQUAL(pszExt, "geo"))
158
        return FALSE;
159
#endif
160
161
13.6k
    if (!STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 56, "NADGRD") &&
162
13.6k
        !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 56, "GEOGRD"))
163
13.6k
        return FALSE;
164
165
28
    return TRUE;
166
13.6k
}
167
168
/************************************************************************/
169
/*                                Open()                                */
170
/************************************************************************/
171
172
GDALDataset *LOSLASDataset::Open(GDALOpenInfo *poOpenInfo)
173
174
14
{
175
14
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
176
0
        return nullptr;
177
178
    /* -------------------------------------------------------------------- */
179
    /*      Confirm the requested access is supported.                      */
180
    /* -------------------------------------------------------------------- */
181
14
    if (poOpenInfo->eAccess == GA_Update)
182
0
    {
183
0
        ReportUpdateNotSupportedByDriver("LOSLAS");
184
0
        return nullptr;
185
0
    }
186
187
    /* -------------------------------------------------------------------- */
188
    /*      Create a corresponding GDALDataset.                             */
189
    /* -------------------------------------------------------------------- */
190
14
    auto poDS = std::make_unique<LOSLASDataset>();
191
14
    std::swap(poDS->fpImage, poOpenInfo->fpL);
192
193
    /* -------------------------------------------------------------------- */
194
    /*      Read the header.                                                */
195
    /* -------------------------------------------------------------------- */
196
14
    CPL_IGNORE_RET_VAL(VSIFSeekL(poDS->fpImage, 64, SEEK_SET));
197
198
14
    CPL_IGNORE_RET_VAL(VSIFReadL(&(poDS->nRasterXSize), 4, 1, poDS->fpImage));
199
14
    CPL_IGNORE_RET_VAL(VSIFReadL(&(poDS->nRasterYSize), 4, 1, poDS->fpImage));
200
201
14
    CPL_LSBPTR32(&(poDS->nRasterXSize));
202
14
    CPL_LSBPTR32(&(poDS->nRasterYSize));
203
204
14
    if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) ||
205
14
        poDS->nRasterXSize > (INT_MAX - 4) / 4)
206
8
    {
207
8
        return nullptr;
208
8
    }
209
210
6
    CPL_IGNORE_RET_VAL(VSIFSeekL(poDS->fpImage, 76, SEEK_SET));
211
212
6
    float min_lon, min_lat, delta_lon, delta_lat;
213
214
6
    CPL_IGNORE_RET_VAL(VSIFReadL(&min_lon, 4, 1, poDS->fpImage));
215
6
    CPL_IGNORE_RET_VAL(VSIFReadL(&delta_lon, 4, 1, poDS->fpImage));
216
6
    CPL_IGNORE_RET_VAL(VSIFReadL(&min_lat, 4, 1, poDS->fpImage));
217
6
    CPL_IGNORE_RET_VAL(VSIFReadL(&delta_lat, 4, 1, poDS->fpImage));
218
219
6
    CPL_LSBPTR32(&min_lon);
220
6
    CPL_LSBPTR32(&delta_lon);
221
6
    CPL_LSBPTR32(&min_lat);
222
6
    CPL_LSBPTR32(&delta_lat);
223
224
6
    poDS->nRecordLength = poDS->nRasterXSize * 4 + 4;
225
226
    /* -------------------------------------------------------------------- */
227
    /*      Create band information object.                                 */
228
    /*                                                                      */
229
    /*      Note we are setting up to read from the last image record to    */
230
    /*      the first since the data comes with the southern most record    */
231
    /*      first, not the northernmost like we would want.                 */
232
    /* -------------------------------------------------------------------- */
233
6
    auto poBand = RawRasterBand::Create(
234
6
        poDS.get(), 1, poDS->fpImage,
235
6
        static_cast<vsi_l_offset>(poDS->nRasterYSize) * poDS->nRecordLength + 4,
236
6
        4, -1 * poDS->nRecordLength, GDT_Float32,
237
6
        RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN,
238
6
        RawRasterBand::OwnFP::NO);
239
6
    if (!poBand)
240
0
        return nullptr;
241
6
    poDS->SetBand(1, std::move(poBand));
242
243
6
    if (poOpenInfo->IsExtensionEqualToCI("las"))
244
0
    {
245
0
        poDS->GetRasterBand(1)->SetDescription("Latitude Offset (arc seconds)");
246
0
    }
247
6
    else if (poOpenInfo->IsExtensionEqualToCI("los"))
248
0
    {
249
0
        poDS->GetRasterBand(1)->SetDescription(
250
0
            "Longitude Offset (arc seconds)");
251
0
        poDS->GetRasterBand(1)->SetMetadataItem("positive_value", "west");
252
0
    }
253
6
    else if (poOpenInfo->IsExtensionEqualToCI("geo"))
254
0
    {
255
0
        poDS->GetRasterBand(1)->SetDescription("Geoid undulation (meters)");
256
0
    }
257
258
    /* -------------------------------------------------------------------- */
259
    /*      Setup georeferencing.                                           */
260
    /* -------------------------------------------------------------------- */
261
6
    poDS->adfGeoTransform[0] = min_lon - delta_lon * 0.5;
262
6
    poDS->adfGeoTransform[1] = delta_lon;
263
6
    poDS->adfGeoTransform[2] = 0.0;
264
6
    poDS->adfGeoTransform[3] = min_lat + (poDS->nRasterYSize - 0.5) * delta_lat;
265
6
    poDS->adfGeoTransform[4] = 0.0;
266
6
    poDS->adfGeoTransform[5] = -1.0 * delta_lat;
267
268
    /* -------------------------------------------------------------------- */
269
    /*      Initialize any PAM information.                                 */
270
    /* -------------------------------------------------------------------- */
271
6
    poDS->SetDescription(poOpenInfo->pszFilename);
272
6
    poDS->TryLoadXML();
273
274
    /* -------------------------------------------------------------------- */
275
    /*      Check for overviews.                                            */
276
    /* -------------------------------------------------------------------- */
277
6
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
278
279
6
    return poDS.release();
280
6
}
281
282
/************************************************************************/
283
/*                          GetGeoTransform()                           */
284
/************************************************************************/
285
286
CPLErr LOSLASDataset::GetGeoTransform(double *padfTransform)
287
288
6
{
289
6
    memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
290
6
    return CE_None;
291
6
}
292
293
/************************************************************************/
294
/*                        GDALRegister_LOSLAS()                         */
295
/************************************************************************/
296
297
void GDALRegister_LOSLAS()
298
299
2
{
300
2
    if (GDALGetDriverByName("LOSLAS") != nullptr)
301
0
        return;
302
303
2
    GDALDriver *poDriver = new GDALDriver();
304
305
2
    poDriver->SetDescription("LOSLAS");
306
2
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
307
2
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
308
2
                              "NADCON .los/.las Datum Grid Shift");
309
2
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
310
311
2
    poDriver->pfnOpen = LOSLASDataset::Open;
312
2
    poDriver->pfnIdentify = LOSLASDataset::Identify;
313
314
2
    GetGDALDriverManager()->RegisterDriver(poDriver);
315
2
}