Coverage Report

Created: 2026-08-14 09:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/ntf/ogrntfdatasource.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  UK NTF Reader
4
 * Purpose:  Implements OGRNTFDataSource class
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 1999, Frank Warmerdam
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "ntf.h"
14
#include "cpl_conv.h"
15
#include "cpl_string.h"
16
17
/************************************************************************/
18
/*                          OGRNTFDataSource()                          */
19
/************************************************************************/
20
21
OGRNTFDataSource::OGRNTFDataSource()
22
12.8k
    : nLayers(0), papoLayers(nullptr), poFCLayer(nullptr), iCurrentFC(0),
23
12.8k
      iCurrentReader(-1), nCurrentPos(0), nCurrentFID(0), nNTFFileCount(0),
24
12.8k
      papoNTFFileReader(nullptr), nFCCount(0), papszFCNum(nullptr),
25
12.8k
      papszFCName(nullptr),
26
12.8k
      poSpatialRef(new OGRSpatialReference(
27
12.8k
          "PROJCS[\"OSGB 1936 / British National Grid\",GEOGCS[\"OSGB 1936\","
28
12.8k
          "DATUM[\"OSGB_1936\",SPHEROID[\"Airy 1830\",6377563.396,299.3249646,"
29
12.8k
          "AUTHORITY[\"EPSG\",\"7001\"]],AUTHORITY[\"EPSG\",\"6277\"]],"
30
12.8k
          "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
31
12.8k
          "UNIT[\"degree\",0.0174532925199433],AUTHORITY[\"EPSG\",\"4277\"]],"
32
12.8k
          "PROJECTION[\"Transverse_Mercator\"],"
33
12.8k
          "PARAMETER[\"latitude_of_origin\",49],"
34
12.8k
          "PARAMETER[\"central_meridian\",-2],"
35
12.8k
          "PARAMETER[\"scale_factor\",0.999601272],"
36
12.8k
          "PARAMETER[\"false_easting\",400000],"
37
12.8k
          "PARAMETER[\"false_northing\",-100000],"
38
12.8k
          "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],"
39
12.8k
          "AUTHORITY[\"EPSG\",\"27700\"]]")),
40
12.8k
      papszOptions(nullptr)
41
12.8k
{
42
12.8k
    poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
43
44
    /* -------------------------------------------------------------------- */
45
    /*      Allow initialization of options from the environment.           */
46
    /* -------------------------------------------------------------------- */
47
12.8k
    if (getenv("OGR_NTF_OPTIONS") != nullptr)
48
0
    {
49
0
        papszOptions = CSLTokenizeStringComplex(getenv("OGR_NTF_OPTIONS"), ",",
50
0
                                                FALSE, FALSE);
51
0
    }
52
12.8k
}
53
54
/************************************************************************/
55
/*                         ~OGRNTFDataSource()                          */
56
/************************************************************************/
57
58
OGRNTFDataSource::~OGRNTFDataSource()
59
60
12.8k
{
61
12.8k
    for (int i = 0; i < nNTFFileCount; i++)
62
0
        delete papoNTFFileReader[i];
63
64
12.8k
    CPLFree(papoNTFFileReader);
65
66
12.8k
    for (int i = 0; i < nLayers; i++)
67
0
        delete papoLayers[i];
68
69
12.8k
    if (poFCLayer != nullptr)
70
0
        delete poFCLayer;
71
72
12.8k
    CPLFree(papoLayers);
73
74
12.8k
    CSLDestroy(papszOptions);
75
76
12.8k
    CSLDestroy(papszFCNum);
77
12.8k
    CSLDestroy(papszFCName);
78
79
12.8k
    if (poSpatialRef)
80
12.8k
        poSpatialRef->Release();
81
12.8k
}
82
83
/************************************************************************/
84
/*                           TestCapability()                           */
85
/************************************************************************/
86
87
int OGRNTFDataSource::TestCapability(const char *pszCap) const
88
89
0
{
90
0
    if (EQUAL(pszCap, ODsCZGeometries))
91
0
        return true;
92
93
0
    return false;
94
0
}
95
96
/************************************************************************/
97
/*                           GetNamedLayer()                            */
98
/************************************************************************/
99
100
OGRNTFLayer *OGRNTFDataSource::GetNamedLayer(const char *pszNameIn)
101
102
0
{
103
0
    for (int i = 0; i < nLayers; i++)
104
0
    {
105
0
        if (EQUAL(papoLayers[i]->GetLayerDefn()->GetName(), pszNameIn))
106
0
            return static_cast<OGRNTFLayer *>(papoLayers[i]);
107
0
    }
108
109
0
    return nullptr;
110
0
}
111
112
/************************************************************************/
113
/*                              AddLayer()                              */
114
/************************************************************************/
115
116
void OGRNTFDataSource::AddLayer(OGRLayer *poNewLayer)
117
118
0
{
119
0
    papoLayers = static_cast<OGRLayer **>(
120
0
        CPLRealloc(papoLayers, sizeof(void *) * ++nLayers));
121
122
0
    papoLayers[nLayers - 1] = poNewLayer;
123
0
}
124
125
/************************************************************************/
126
/*                              GetLayer()                              */
127
/************************************************************************/
128
129
OGRLayer *OGRNTFDataSource::GetLayer(int iLayer) const
130
131
0
{
132
0
    if (iLayer < 0 || iLayer > nLayers)
133
0
        return nullptr;
134
0
    else if (iLayer == nLayers)
135
0
        return poFCLayer;
136
0
    else
137
0
        return papoLayers[iLayer];
138
0
}
139
140
/************************************************************************/
141
/*                           GetLayerCount()                            */
142
/************************************************************************/
143
144
int OGRNTFDataSource::GetLayerCount() const
145
146
0
{
147
0
    if (poFCLayer == nullptr)
148
0
        return nLayers;
149
0
    else
150
0
        return nLayers + 1;
151
0
}
152
153
/************************************************************************/
154
/*                                Open()                                */
155
/************************************************************************/
156
157
int OGRNTFDataSource::Open(const char *pszFilename, int bTestOpen,
158
                           char **papszLimitedFileList)
159
160
12.8k
{
161
12.8k
    VSIStatBufL stat;
162
12.8k
    char **papszFileList = nullptr;
163
164
    /* -------------------------------------------------------------------- */
165
    /*      Is the given path a directory or a regular file?                */
166
    /* -------------------------------------------------------------------- */
167
12.8k
    if (VSIStatL(pszFilename, &stat) != 0 ||
168
9.03k
        (!VSI_ISDIR(stat.st_mode) && !VSI_ISREG(stat.st_mode)))
169
3.82k
    {
170
3.82k
        if (!bTestOpen)
171
0
            CPLError(CE_Failure, CPLE_AppDefined,
172
0
                     "%s is neither a file or directory, NTF access failed.\n",
173
0
                     pszFilename);
174
175
3.82k
        return FALSE;
176
3.82k
    }
177
178
    /* -------------------------------------------------------------------- */
179
    /*      Build a list of filenames we figure are NTF files.              */
180
    /* -------------------------------------------------------------------- */
181
9.03k
    if (VSI_ISREG(stat.st_mode))
182
687
    {
183
687
        papszFileList = CSLAddString(nullptr, pszFilename);
184
687
    }
185
8.34k
    else
186
8.34k
    {
187
8.34k
        char **candidateFileList = VSIReadDir(pszFilename);
188
189
8.34k
        for (int i = 0;
190
100k
             candidateFileList != nullptr && candidateFileList[i] != nullptr;
191
92.4k
             i++)
192
92.4k
        {
193
92.4k
            if (papszLimitedFileList != nullptr &&
194
0
                CSLFindString(papszLimitedFileList, candidateFileList[i]) == -1)
195
0
            {
196
0
                continue;
197
0
            }
198
199
92.4k
            if (strlen(candidateFileList[i]) > 4 &&
200
87.6k
                STARTS_WITH_CI(candidateFileList[i] +
201
92.4k
                                   strlen(candidateFileList[i]) - 4,
202
92.4k
                               ".ntf"))
203
0
            {
204
0
                papszFileList =
205
0
                    CSLAddString(papszFileList,
206
0
                                 CPLFormFilenameSafe(
207
0
                                     pszFilename, candidateFileList[i], nullptr)
208
0
                                     .c_str());
209
0
            }
210
92.4k
        }
211
212
8.34k
        CSLDestroy(candidateFileList);
213
214
8.34k
        if (CSLCount(papszFileList) == 0)
215
8.34k
        {
216
8.34k
            if (!bTestOpen)
217
0
                CPLError(CE_Failure, CPLE_OpenFailed,
218
0
                         "No candidate NTF files (.ntf) found in\n"
219
0
                         "directory: %s",
220
0
                         pszFilename);
221
8.34k
            CSLDestroy(papszFileList);
222
8.34k
            return FALSE;
223
8.34k
        }
224
8.34k
    }
225
226
    /* -------------------------------------------------------------------- */
227
    /*      Loop over all these files trying to open them.  In testopen     */
228
    /*      mode we first read the first 80 characters, to verify that      */
229
    /*      it looks like an NTF file.  Note that we don't keep the file    */
230
    /*      open ... we don't want to occupy a lot of file handles when      */
231
    /*      handling a whole directory.                                     */
232
    /* -------------------------------------------------------------------- */
233
687
    papoNTFFileReader = static_cast<NTFFileReader **>(
234
687
        CPLCalloc(sizeof(void *), CSLCount(papszFileList)));
235
236
1.36k
    for (int i = 0; papszFileList != nullptr && papszFileList[i] != nullptr;
237
687
         i++)
238
687
    {
239
687
        if (bTestOpen)
240
687
        {
241
687
            VSILFILE *fp = VSIFOpenL(papszFileList[i], "rb");
242
687
            if (fp == nullptr)
243
258
                continue;
244
245
429
            char szHeader[80] = {};
246
429
            if (VSIFReadL(szHeader, 80, 1, fp) < 1)
247
423
            {
248
423
                VSIFCloseL(fp);
249
423
                continue;
250
423
            }
251
252
6
            VSIFCloseL(fp);
253
254
6
            if (!STARTS_WITH_CI(szHeader, "01"))
255
0
                continue;
256
257
6
            int j = 0;  // Used after for.
258
413
            for (; j < 80; j++)
259
413
            {
260
413
                if (szHeader[j] == 10 || szHeader[j] == 13)
261
6
                    break;
262
413
            }
263
264
6
            if (j == 80 || (j > 0 && szHeader[j - 1] != '%'))
265
0
                continue;
266
6
        }
267
268
6
        NTFFileReader *poFR = new NTFFileReader(this);
269
270
6
        if (!poFR->Open(papszFileList[i]))
271
6
        {
272
6
            delete poFR;
273
6
            CSLDestroy(papszFileList);
274
275
6
            return FALSE;
276
6
        }
277
278
0
        poFR->SetBaseFID(nNTFFileCount * 1000000 + 1);
279
0
        poFR->Close();
280
281
0
        EnsureTileNameUnique(poFR);
282
283
0
        papoNTFFileReader[nNTFFileCount++] = poFR;
284
0
    }
285
286
681
    CSLDestroy(papszFileList);
287
288
681
    if (nNTFFileCount == 0)
289
681
        return FALSE;
290
291
    /* -------------------------------------------------------------------- */
292
    /*      Establish generic layers.                                       */
293
    /* -------------------------------------------------------------------- */
294
0
    EstablishGenericLayers();
295
296
    /* -------------------------------------------------------------------- */
297
    /*      Loop over all the files, collecting a unique feature class      */
298
    /*      listing.                                                        */
299
    /* -------------------------------------------------------------------- */
300
0
    for (int iSrcFile = 0; iSrcFile < nNTFFileCount; iSrcFile++)
301
0
    {
302
0
        NTFFileReader *poSrcReader = papoNTFFileReader[iSrcFile];
303
304
0
        for (int iSrcFC = 0; iSrcFC < poSrcReader->GetFCCount(); iSrcFC++)
305
0
        {
306
0
            char *pszSrcFCName = nullptr;
307
0
            char *pszSrcFCNum = nullptr;
308
309
0
            poSrcReader->GetFeatureClass(iSrcFC, &pszSrcFCNum, &pszSrcFCName);
310
311
0
            int iDstFC = 0;
312
0
            for (; iDstFC < nFCCount; iDstFC++)
313
0
            {
314
0
                if (EQUAL(pszSrcFCNum, papszFCNum[iDstFC]))
315
0
                    break;
316
0
            }
317
318
0
            if (iDstFC >= nFCCount)
319
0
            {
320
0
                nFCCount++;
321
0
                papszFCNum = CSLAddString(papszFCNum, pszSrcFCNum);
322
0
                papszFCName = CSLAddString(papszFCName, pszSrcFCName);
323
0
            }
324
0
        }
325
0
    }
326
327
    /* -------------------------------------------------------------------- */
328
    /*      Create a new layer specifically for feature classes.            */
329
    /* -------------------------------------------------------------------- */
330
0
    if (nFCCount > 0)
331
0
        poFCLayer = new OGRNTFFeatureClassLayer(this);
332
0
    else
333
0
        poFCLayer = nullptr;
334
335
0
    return TRUE;
336
681
}
337
338
/************************************************************************/
339
/*                            ResetReading()                            */
340
/*                                                                      */
341
/*      Cleanup, and start over.                                        */
342
/************************************************************************/
343
344
void OGRNTFDataSource::ResetReading()
345
346
0
{
347
0
    for (int i = 0; i < nNTFFileCount; i++)
348
0
        papoNTFFileReader[i]->Close();
349
350
0
    iCurrentReader = -1;
351
0
    nCurrentPos = (vsi_l_offset)-1;
352
0
    nCurrentFID = 1;
353
0
    iCurrentFC = 0;
354
0
}
355
356
/************************************************************************/
357
/*                           GetNextFeature()                           */
358
/************************************************************************/
359
360
OGRFeature *OGRNTFDataSource::GetNextFeature(OGRLayer **ppoBelongingLayer,
361
                                             double *pdfProgressPct,
362
                                             GDALProgressFunc /* pfnProgress */,
363
                                             void * /* pProgressData */)
364
365
0
{
366
0
    if (pdfProgressPct != nullptr)
367
0
        *pdfProgressPct = 0.0;
368
0
    if (ppoBelongingLayer != nullptr)
369
0
        *ppoBelongingLayer = nullptr;
370
371
0
    OGRFeature *poFeature = nullptr;
372
373
    /* -------------------------------------------------------------------- */
374
    /*      If we have already read all the conventional features, we       */
375
    /*      should try and return feature class features.                   */
376
    /* -------------------------------------------------------------------- */
377
0
    if (iCurrentReader == nNTFFileCount)
378
0
    {
379
0
        if (iCurrentFC < nFCCount)
380
0
            return poFCLayer->GetFeature(iCurrentFC++);
381
0
        else
382
0
            return nullptr;
383
0
    }
384
385
    /* -------------------------------------------------------------------- */
386
    /*      Do we need to open a file?                                      */
387
    /* -------------------------------------------------------------------- */
388
0
    if (iCurrentReader == -1)
389
0
    {
390
0
        iCurrentReader++;
391
0
        nCurrentPos = (vsi_l_offset)-1;
392
0
    }
393
394
0
    if (papoNTFFileReader[iCurrentReader]->GetFP() == nullptr)
395
0
    {
396
0
        papoNTFFileReader[iCurrentReader]->Open();
397
0
    }
398
399
    /* -------------------------------------------------------------------- */
400
    /*      Ensure we are reading on from the same point we were reading    */
401
    /*      from for the last feature, even if some other access            */
402
    /*      mechanism has moved the file pointer.                           */
403
    /* -------------------------------------------------------------------- */
404
0
    if (nCurrentPos != (vsi_l_offset)-1)
405
0
        papoNTFFileReader[iCurrentReader]->SetFPPos(nCurrentPos, nCurrentFID);
406
407
    /* -------------------------------------------------------------------- */
408
    /*      Read a feature.  If we get NULL the file must be all            */
409
    /*      consumed, advance to the next file.                             */
410
    /* -------------------------------------------------------------------- */
411
0
    poFeature = papoNTFFileReader[iCurrentReader]->ReadOGRFeature();
412
0
    if (poFeature == nullptr)
413
0
    {
414
0
        papoNTFFileReader[iCurrentReader]->Close();
415
0
        if (GetOption("CACHING") != nullptr &&
416
0
            EQUAL(GetOption("CACHING"), "OFF"))
417
0
            papoNTFFileReader[iCurrentReader]->DestroyIndex();
418
419
0
        iCurrentReader++;
420
0
        nCurrentPos = (vsi_l_offset)-1;
421
0
        nCurrentFID = 1;
422
423
0
        poFeature = GetNextFeature(nullptr, nullptr, nullptr, nullptr);
424
0
    }
425
0
    else
426
0
    {
427
0
        papoNTFFileReader[iCurrentReader]->GetFPPos(&nCurrentPos, &nCurrentFID);
428
0
    }
429
430
0
    return poFeature;
431
0
}
432
433
/************************************************************************/
434
/*                          GetFeatureClass()                           */
435
/************************************************************************/
436
437
int OGRNTFDataSource::GetFeatureClass(int iFCIndex, char **ppszFCId,
438
                                      char **ppszFCName)
439
440
0
{
441
0
    if (iFCIndex < 0 || iFCIndex >= nFCCount)
442
0
    {
443
0
        *ppszFCId = nullptr;
444
0
        *ppszFCName = nullptr;
445
0
        return FALSE;
446
0
    }
447
0
    else
448
0
    {
449
0
        *ppszFCId = papszFCNum[iFCIndex];
450
0
        *ppszFCName = papszFCName[iFCIndex];
451
0
        return TRUE;
452
0
    }
453
0
}
454
455
/************************************************************************/
456
/*                             SetOptions()                             */
457
/************************************************************************/
458
459
void OGRNTFDataSource::SetOptionList(char **papszNewOptions)
460
461
0
{
462
0
    CSLDestroy(papszOptions);
463
0
    papszOptions = CSLDuplicate(papszNewOptions);
464
0
}
465
466
/************************************************************************/
467
/*                             GetOption()                              */
468
/************************************************************************/
469
470
const char *OGRNTFDataSource::GetOption(const char *pszOption)
471
472
6
{
473
6
    return CSLFetchNameValue(papszOptions, pszOption);
474
6
}
475
476
/************************************************************************/
477
/*                        EnsureTileNameUnique()                        */
478
/*                                                                      */
479
/*      This method is called with an NTFFileReader to ensure that      */
480
/*      its tilename is unique relative to all the readers already      */
481
/*      assigned to this data source.  If not, a unique name is         */
482
/*      selected for it and assigned.  This method should not be        */
483
/*      called with readers that are already attached to the data      */
484
/*      source.                                                         */
485
/************************************************************************/
486
487
void OGRNTFDataSource::EnsureTileNameUnique(NTFFileReader *poNewReader)
488
489
0
{
490
0
    int iSequenceNumber = -1;
491
0
    bool bIsUnique = false;
492
0
    char szCandidateName[12] = {};
493
494
0
    do
495
0
    {
496
0
        bIsUnique = TRUE;
497
0
        if (iSequenceNumber++ == -1)
498
0
            strncpy(szCandidateName, poNewReader->GetTileName(),
499
0
                    sizeof(szCandidateName) - 1);
500
0
        else
501
0
            snprintf(szCandidateName, sizeof(szCandidateName), "%010d",
502
0
                     iSequenceNumber);
503
504
0
        for (int iReader = 0; iReader < nNTFFileCount && bIsUnique; iReader++)
505
0
        {
506
0
            const char *pszTileName = GetFileReader(iReader)->GetTileName();
507
0
            if (pszTileName != nullptr &&
508
0
                strcmp(szCandidateName, pszTileName) == 0)
509
0
            {
510
0
                bIsUnique = FALSE;
511
0
            }
512
0
        }
513
0
    } while (!bIsUnique);
514
515
0
    if (iSequenceNumber > 0)
516
0
    {
517
0
        poNewReader->OverrideTileName(szCandidateName);
518
0
        CPLError(CE_Warning, CPLE_AppDefined,
519
0
                 "Forcing TILE_REF to `%s' on file %s\n"
520
0
                 "to avoid conflict with other tiles in this data source.",
521
0
                 szCandidateName, poNewReader->GetFilename());
522
0
    }
523
0
}