Coverage Report

Created: 2025-06-09 07:42

/src/gdal/frmts/raw/ndfdataset.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  NDF Driver
4
 * Purpose:  Implementation of NLAPS Data Format read support.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2005, Frank Warmerdam
9
 * Copyright (c) 2008-2011, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_string.h"
15
#include "gdal_frmts.h"
16
#include "ogr_spatialref.h"
17
#include "rawdataset.h"
18
19
/************************************************************************/
20
/* ==================================================================== */
21
/*                              NDFDataset                              */
22
/* ==================================================================== */
23
/************************************************************************/
24
25
class NDFDataset final : public RawDataset
26
{
27
    double adfGeoTransform[6];
28
29
    OGRSpatialReference m_oSRS{};
30
    char **papszExtraFiles;
31
32
    char **papszHeader;
33
    const char *Get(const char *pszKey, const char *pszDefault);
34
35
    CPL_DISALLOW_COPY_ASSIGN(NDFDataset)
36
37
    CPLErr Close() override;
38
39
  public:
40
    NDFDataset();
41
    ~NDFDataset() override;
42
43
    CPLErr GetGeoTransform(double *padfTransform) override;
44
45
    const OGRSpatialReference *GetSpatialRef() const override
46
0
    {
47
0
        return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
48
0
    }
49
50
    char **GetFileList(void) override;
51
52
    static GDALDataset *Open(GDALOpenInfo *);
53
    static int Identify(GDALOpenInfo *);
54
};
55
56
/************************************************************************/
57
/*                            NDFDataset()                             */
58
/************************************************************************/
59
60
0
NDFDataset::NDFDataset() : papszExtraFiles(nullptr), papszHeader(nullptr)
61
0
{
62
0
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
63
0
    adfGeoTransform[0] = 0.0;
64
0
    adfGeoTransform[1] = 1.0;
65
0
    adfGeoTransform[2] = 0.0;
66
0
    adfGeoTransform[3] = 0.0;
67
0
    adfGeoTransform[4] = 0.0;
68
0
    adfGeoTransform[5] = 1.0;
69
0
}
70
71
/************************************************************************/
72
/*                            ~NDFDataset()                             */
73
/************************************************************************/
74
75
NDFDataset::~NDFDataset()
76
77
0
{
78
0
    NDFDataset::Close();
79
0
}
80
81
/************************************************************************/
82
/*                              Close()                                 */
83
/************************************************************************/
84
85
CPLErr NDFDataset::Close()
86
0
{
87
0
    CPLErr eErr = CE_None;
88
0
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
89
0
    {
90
0
        if (NDFDataset::FlushCache(true) != CE_None)
91
0
            eErr = CE_Failure;
92
93
0
        CSLDestroy(papszHeader);
94
0
        CSLDestroy(papszExtraFiles);
95
96
0
        if (GDALPamDataset::Close() != CE_None)
97
0
            eErr = CE_Failure;
98
0
    }
99
0
    return eErr;
100
0
}
101
102
/************************************************************************/
103
/*                          GetGeoTransform()                           */
104
/************************************************************************/
105
106
CPLErr NDFDataset::GetGeoTransform(double *padfTransform)
107
108
0
{
109
0
    memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
110
0
    return CE_None;
111
0
}
112
113
/************************************************************************/
114
/*                                Get()                                 */
115
/*                                                                      */
116
/*      Fetch a value from the header by keyword.                       */
117
/************************************************************************/
118
119
const char *NDFDataset::Get(const char *pszKey, const char *pszDefault)
120
121
0
{
122
0
    const char *pszResult = CSLFetchNameValue(papszHeader, pszKey);
123
124
0
    if (pszResult == nullptr)
125
0
        return pszDefault;
126
127
0
    return pszResult;
128
0
}
129
130
/************************************************************************/
131
/*                            GetFileList()                             */
132
/************************************************************************/
133
134
char **NDFDataset::GetFileList()
135
136
0
{
137
    // Main data file, etc.
138
0
    char **papszFileList = GDALPamDataset::GetFileList();
139
140
    // Header file.
141
0
    papszFileList = CSLInsertStrings(papszFileList, -1, papszExtraFiles);
142
143
0
    return papszFileList;
144
0
}
145
146
/************************************************************************/
147
/*                            Identify()                                */
148
/************************************************************************/
149
150
int NDFDataset::Identify(GDALOpenInfo *poOpenInfo)
151
152
475k
{
153
    /* -------------------------------------------------------------------- */
154
    /*      The user must select the header file (i.e. .H1).                */
155
    /* -------------------------------------------------------------------- */
156
475k
    return poOpenInfo->nHeaderBytes >= 50 &&
157
475k
           (STARTS_WITH_CI(
158
1.61k
                reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
159
1.61k
                "NDF_REVISION=2") ||
160
1.61k
            STARTS_WITH_CI(
161
1.61k
                reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
162
1.61k
                "NDF_REVISION=0"));
163
475k
}
164
165
/************************************************************************/
166
/*                                Open()                                */
167
/************************************************************************/
168
169
GDALDataset *NDFDataset::Open(GDALOpenInfo *poOpenInfo)
170
171
0
{
172
    /* -------------------------------------------------------------------- */
173
    /*      The user must select the header file (i.e. .H1).                */
174
    /* -------------------------------------------------------------------- */
175
0
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
176
0
        return nullptr;
177
178
    /* -------------------------------------------------------------------- */
179
    /*      Confirm the requested access is supported.                      */
180
    /* -------------------------------------------------------------------- */
181
0
    if (poOpenInfo->eAccess == GA_Update)
182
0
    {
183
0
        ReportUpdateNotSupportedByDriver("NDF");
184
0
        return nullptr;
185
0
    }
186
    /* -------------------------------------------------------------------- */
187
    /*      Read and process the header into a local name/value             */
188
    /*      stringlist.  We just take off the trailing semicolon.  The      */
189
    /*      keyword is already separated from the value by an equal         */
190
    /*      sign.                                                           */
191
    /* -------------------------------------------------------------------- */
192
193
0
    const char *pszLine = nullptr;
194
0
    const int nHeaderMax = 1000;
195
0
    int nHeaderLines = 0;
196
0
    char **papszHeader =
197
0
        static_cast<char **>(CPLMalloc(sizeof(char *) * (nHeaderMax + 1)));
198
199
0
    while (nHeaderLines < nHeaderMax &&
200
0
           (pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr &&
201
0
           !EQUAL(pszLine, "END_OF_HDR;"))
202
0
    {
203
0
        if (strstr(pszLine, "=") == nullptr)
204
0
            break;
205
206
0
        char *pszFixed = CPLStrdup(pszLine);
207
0
        if (pszFixed[strlen(pszFixed) - 1] == ';')
208
0
            pszFixed[strlen(pszFixed) - 1] = '\0';
209
210
0
        papszHeader[nHeaderLines++] = pszFixed;
211
0
        papszHeader[nHeaderLines] = nullptr;
212
0
    }
213
0
    CPL_IGNORE_RET_VAL(VSIFCloseL(poOpenInfo->fpL));
214
0
    poOpenInfo->fpL = nullptr;
215
216
0
    if (CSLFetchNameValue(papszHeader, "PIXELS_PER_LINE") == nullptr ||
217
0
        CSLFetchNameValue(papszHeader, "LINES_PER_DATA_FILE") == nullptr ||
218
0
        CSLFetchNameValue(papszHeader, "BITS_PER_PIXEL") == nullptr ||
219
0
        CSLFetchNameValue(papszHeader, "PIXEL_FORMAT") == nullptr)
220
0
    {
221
0
        CPLError(CE_Failure, CPLE_AppDefined,
222
0
                 "Dataset appears to be NDF but is missing a required field.");
223
0
        CSLDestroy(papszHeader);
224
0
        return nullptr;
225
0
    }
226
227
0
    if (!EQUAL(CSLFetchNameValue(papszHeader, "PIXEL_FORMAT"), "BYTE") ||
228
0
        !EQUAL(CSLFetchNameValue(papszHeader, "BITS_PER_PIXEL"), "8"))
229
0
    {
230
0
        CPLError(CE_Failure, CPLE_AppDefined,
231
0
                 "Currently NDF driver supports only 8bit BYTE format.");
232
0
        CSLDestroy(papszHeader);
233
0
        return nullptr;
234
0
    }
235
236
    /* -------------------------------------------------------------------- */
237
    /*      Confirm the requested access is supported.                      */
238
    /* -------------------------------------------------------------------- */
239
0
    if (poOpenInfo->eAccess == GA_Update)
240
0
    {
241
0
        CSLDestroy(papszHeader);
242
0
        ReportUpdateNotSupportedByDriver("NDF");
243
0
        return nullptr;
244
0
    }
245
246
    /* -------------------------------------------------------------------- */
247
    /*      Create a corresponding GDALDataset.                             */
248
    /* -------------------------------------------------------------------- */
249
0
    auto poDS = std::make_unique<NDFDataset>();
250
0
    poDS->papszHeader = papszHeader;
251
252
0
    poDS->nRasterXSize = atoi(poDS->Get("PIXELS_PER_LINE", ""));
253
0
    poDS->nRasterYSize = atoi(poDS->Get("LINES_PER_DATA_FILE", ""));
254
255
    /* -------------------------------------------------------------------- */
256
    /*      Create a raw raster band for each file.                         */
257
    /* -------------------------------------------------------------------- */
258
0
    const char *pszBand =
259
0
        CSLFetchNameValue(papszHeader, "NUMBER_OF_BANDS_IN_VOLUME");
260
0
    if (pszBand == nullptr)
261
0
    {
262
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find band count");
263
0
        return nullptr;
264
0
    }
265
0
    const int nBands = atoi(pszBand);
266
267
0
    if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) ||
268
0
        !GDALCheckBandCount(nBands, FALSE))
269
0
    {
270
0
        return nullptr;
271
0
    }
272
273
0
    for (int iBand = 0; iBand < nBands; iBand++)
274
0
    {
275
0
        char szKey[100];
276
0
        snprintf(szKey, sizeof(szKey), "BAND%d_FILENAME", iBand + 1);
277
0
        CPLString osFilename = poDS->Get(szKey, "");
278
279
        // NDF1 file do not include the band filenames.
280
0
        if (osFilename.empty())
281
0
        {
282
0
            char szBandExtension[15];
283
0
            snprintf(szBandExtension, sizeof(szBandExtension), "I%d",
284
0
                     iBand + 1);
285
0
            osFilename =
286
0
                CPLResetExtensionSafe(poOpenInfo->pszFilename, szBandExtension);
287
0
        }
288
0
        else
289
0
        {
290
0
            CPLString osBasePath = CPLGetPathSafe(poOpenInfo->pszFilename);
291
0
            osFilename = CPLFormFilenameSafe(osBasePath, osFilename, nullptr);
292
0
        }
293
294
0
        VSILFILE *fpRaw = VSIFOpenL(osFilename, "rb");
295
0
        if (fpRaw == nullptr)
296
0
        {
297
0
            CPLError(CE_Failure, CPLE_AppDefined,
298
0
                     "Failed to open band file: %s", osFilename.c_str());
299
0
            return nullptr;
300
0
        }
301
0
        poDS->papszExtraFiles = CSLAddString(poDS->papszExtraFiles, osFilename);
302
303
0
        auto poBand = RawRasterBand::Create(
304
0
            poDS.get(), iBand + 1, fpRaw, 0, 1, poDS->nRasterXSize, GDT_Byte,
305
0
            RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN,
306
0
            RawRasterBand::OwnFP::YES);
307
0
        if (!poBand)
308
0
            return nullptr;
309
310
0
        snprintf(szKey, sizeof(szKey), "BAND%d_NAME", iBand + 1);
311
0
        poBand->SetDescription(poDS->Get(szKey, ""));
312
313
0
        snprintf(szKey, sizeof(szKey), "BAND%d_WAVELENGTHS", iBand + 1);
314
0
        poBand->SetMetadataItem("WAVELENGTHS", poDS->Get(szKey, ""));
315
316
0
        snprintf(szKey, sizeof(szKey), "BAND%d_RADIOMETRIC_GAINS/BIAS",
317
0
                 iBand + 1);
318
0
        poBand->SetMetadataItem("RADIOMETRIC_GAINS_BIAS", poDS->Get(szKey, ""));
319
320
0
        poDS->SetBand(iBand + 1, std::move(poBand));
321
0
    }
322
323
    /* -------------------------------------------------------------------- */
324
    /*      Fetch and parse USGS projection parameters.                     */
325
    /* -------------------------------------------------------------------- */
326
0
    double adfUSGSParams[15] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
327
0
    const CPLStringList aosParamTokens(CSLTokenizeStringComplex(
328
0
        poDS->Get("USGS_PROJECTION_NUMBER", ""), ",", FALSE, TRUE));
329
330
0
    if (aosParamTokens.size() >= 15)
331
0
    {
332
0
        for (int i = 0; i < 15; i++)
333
0
            adfUSGSParams[i] = CPLAtof(aosParamTokens[i]);
334
0
    }
335
336
    /* -------------------------------------------------------------------- */
337
    /*      Minimal georef support ... should add full USGS style           */
338
    /*      support at some point.                                          */
339
    /* -------------------------------------------------------------------- */
340
0
    const int nUSGSProjection = atoi(poDS->Get("USGS_PROJECTION_NUMBER", ""));
341
0
    const int nZone = atoi(poDS->Get("USGS_MAP_ZONE", "0"));
342
343
0
    OGRSpatialReference oSRS;
344
0
    oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
345
0
    oSRS.importFromUSGS(nUSGSProjection, nZone, adfUSGSParams, 12);
346
347
0
    const CPLString osDatum = poDS->Get("HORIZONTAL_DATUM", "");
348
0
    if (EQUAL(osDatum, "WGS84") || EQUAL(osDatum, "NAD83") ||
349
0
        EQUAL(osDatum, "NAD27"))
350
0
    {
351
0
        oSRS.SetWellKnownGeogCS(osDatum);
352
0
    }
353
0
    else if (STARTS_WITH_CI(osDatum, "NAD27"))
354
0
    {
355
0
        oSRS.SetWellKnownGeogCS("NAD27");
356
0
    }
357
0
    else
358
0
    {
359
0
        CPLError(CE_Warning, CPLE_AppDefined,
360
0
                 "Unrecognized datum name in NLAPS/NDF file:%s, "
361
0
                 "assuming WGS84.",
362
0
                 osDatum.c_str());
363
0
        oSRS.SetWellKnownGeogCS("WGS84");
364
0
    }
365
366
0
    if (oSRS.GetRoot() != nullptr)
367
0
    {
368
0
        poDS->m_oSRS = std::move(oSRS);
369
0
    }
370
371
    /* -------------------------------------------------------------------- */
372
    /*      Get geotransform.                                               */
373
    /* -------------------------------------------------------------------- */
374
0
    char **papszUL =
375
0
        CSLTokenizeString2(poDS->Get("UPPER_LEFT_CORNER", ""), ",", 0);
376
0
    char **papszUR =
377
0
        CSLTokenizeString2(poDS->Get("UPPER_RIGHT_CORNER", ""), ",", 0);
378
0
    char **papszLL =
379
0
        CSLTokenizeString2(poDS->Get("LOWER_LEFT_CORNER", ""), ",", 0);
380
381
0
    if (CSLCount(papszUL) == 4 && CSLCount(papszUR) == 4 &&
382
0
        CSLCount(papszLL) == 4)
383
0
    {
384
0
        poDS->adfGeoTransform[0] = CPLAtof(papszUL[2]);
385
0
        poDS->adfGeoTransform[1] = (CPLAtof(papszUR[2]) - CPLAtof(papszUL[2])) /
386
0
                                   (poDS->nRasterXSize - 1);
387
0
        poDS->adfGeoTransform[2] = (CPLAtof(papszUR[3]) - CPLAtof(papszUL[3])) /
388
0
                                   (poDS->nRasterXSize - 1);
389
390
0
        poDS->adfGeoTransform[3] = CPLAtof(papszUL[3]);
391
0
        poDS->adfGeoTransform[4] = (CPLAtof(papszLL[2]) - CPLAtof(papszUL[2])) /
392
0
                                   (poDS->nRasterYSize - 1);
393
0
        poDS->adfGeoTransform[5] = (CPLAtof(papszLL[3]) - CPLAtof(papszUL[3])) /
394
0
                                   (poDS->nRasterYSize - 1);
395
396
        // Move origin up-left half a pixel.
397
0
        poDS->adfGeoTransform[0] -= poDS->adfGeoTransform[1] * 0.5;
398
0
        poDS->adfGeoTransform[0] -= poDS->adfGeoTransform[4] * 0.5;
399
0
        poDS->adfGeoTransform[3] -= poDS->adfGeoTransform[2] * 0.5;
400
0
        poDS->adfGeoTransform[3] -= poDS->adfGeoTransform[5] * 0.5;
401
0
    }
402
403
0
    CSLDestroy(papszUL);
404
0
    CSLDestroy(papszLL);
405
0
    CSLDestroy(papszUR);
406
407
    /* -------------------------------------------------------------------- */
408
    /*      Initialize any PAM information.                                 */
409
    /* -------------------------------------------------------------------- */
410
0
    poDS->SetDescription(poOpenInfo->pszFilename);
411
0
    poDS->TryLoadXML();
412
413
    /* -------------------------------------------------------------------- */
414
    /*      Check for overviews.                                            */
415
    /* -------------------------------------------------------------------- */
416
0
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
417
418
0
    return poDS.release();
419
0
}
420
421
/************************************************************************/
422
/*                          GDALRegister_NDF()                          */
423
/************************************************************************/
424
425
void GDALRegister_NDF()
426
427
2
{
428
2
    if (GDALGetDriverByName("NDF") != nullptr)
429
0
        return;
430
431
2
    GDALDriver *poDriver = new GDALDriver();
432
433
2
    poDriver->SetDescription("NDF");
434
2
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
435
2
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "NLAPS Data Format");
436
2
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/ndf.html");
437
2
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
438
439
2
    poDriver->pfnIdentify = NDFDataset::Identify;
440
2
    poDriver->pfnOpen = NDFDataset::Open;
441
442
2
    GetGDALDriverManager()->RegisterDriver(poDriver);
443
2
}