Coverage Report

Created: 2026-03-30 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/ntf/ntffilereader.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  NTF Translator
4
 * Purpose:  NTFFileReader class implementation.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 1999, Frank Warmerdam
9
 * Copyright (c) 2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include <stdarg.h>
15
#include "ntf.h"
16
#include "cpl_conv.h"
17
#include "cpl_string.h"
18
#include "ogr_api.h"
19
20
#include <algorithm>
21
22
0
#define DIGIT_ZERO '0'
23
24
static int DefaultNTFRecordGrouper(NTFFileReader *, NTFRecord **, NTFRecord *);
25
26
/************************************************************************/
27
/*                            NTFFileReader                             */
28
/************************************************************************/
29
30
NTFFileReader::NTFFileReader(OGRNTFDataSource *poDataSource)
31
2
    : pszFilename(nullptr), poDS(poDataSource), fp(nullptr), nFCCount(0),
32
2
      papszFCNum(nullptr), papszFCName(nullptr), nAttCount(0),
33
2
      pasAttDesc(nullptr), pszTileName(nullptr), nCoordWidth(6), nZWidth(6),
34
2
      nNTFLevel(0), dfXYMult(1.0), dfZMult(1.0), dfXOrigin(0), dfYOrigin(0),
35
2
      dfTileXSize(0), dfTileYSize(0), dfScale(0.0), dfPaperToGround(0.0),
36
2
      nStartPos(0), nPreSavedPos(0), nPostSavedPos(0), poSavedRecord(nullptr),
37
2
      nSavedFeatureId(1), nBaseFeatureId(1), nFeatureCount(-1),
38
2
      pszProduct(nullptr), pszPVName(nullptr), nProduct(NPC_UNKNOWN),
39
2
      pfnRecordGrouper(DefaultNTFRecordGrouper), bIndexBuilt(FALSE),
40
2
      bIndexNeeded(FALSE), nRasterXSize(1), nRasterYSize(1), nRasterDataType(1),
41
2
      poRasterLayer(nullptr), panColumnOffset(nullptr), bCacheLines(TRUE),
42
2
      nLineCacheSize(0), papoLineCache(nullptr)
43
2
{
44
2
    apoCGroup[0] = nullptr;
45
2
    apoCGroup[1] = nullptr;
46
2
    apoCGroup[MAX_REC_GROUP] = nullptr;
47
2
    memset(adfGeoTransform, 0, sizeof(adfGeoTransform));
48
2
    memset(apoTypeTranslation, 0, sizeof(apoTypeTranslation));
49
202
    for (int i = 0; i < 100; i++)
50
200
    {
51
200
        anIndexSize[i] = 0;
52
200
        apapoRecordIndex[i] = nullptr;
53
200
    }
54
2
    if (poDS->GetOption("CACHE_LINES") != nullptr &&
55
0
        EQUAL(poDS->GetOption("CACHE_LINES"), "OFF"))
56
0
        bCacheLines = FALSE;
57
2
}
58
59
/************************************************************************/
60
/*                           ~NTFFileReader()                           */
61
/************************************************************************/
62
63
NTFFileReader::~NTFFileReader()
64
65
2
{
66
2
    CacheClean();
67
2
    DestroyIndex();
68
2
    ClearDefs();
69
2
    CPLFree(pszFilename);
70
2
    CPLFree(panColumnOffset);
71
2
}
72
73
/************************************************************************/
74
/*                             SetBaseFID()                             */
75
/************************************************************************/
76
77
void NTFFileReader::SetBaseFID(long nNewBase)
78
79
0
{
80
0
    CPLAssert(nSavedFeatureId == 1);
81
82
0
    nBaseFeatureId = nNewBase;
83
0
    nSavedFeatureId = nBaseFeatureId;
84
0
}
85
86
/************************************************************************/
87
/*                             ClearDefs()                              */
88
/*                                                                      */
89
/*      Clear attribute definitions and feature classes.  All the       */
90
/*      stuff that would have to be cleaned up by Open(), and the       */
91
/*      destructor.                                                     */
92
/************************************************************************/
93
94
void NTFFileReader::ClearDefs()
95
96
4
{
97
4
    Close();
98
99
4
    ClearCGroup();
100
101
4
    CSLDestroy(papszFCNum);
102
4
    papszFCNum = nullptr;
103
4
    CSLDestroy(papszFCName);
104
4
    papszFCName = nullptr;
105
4
    nFCCount = 0;
106
107
4
    for (int i = 0; i < nAttCount; i++)
108
0
    {
109
0
        if (pasAttDesc[i].poCodeList != nullptr)
110
0
            delete pasAttDesc[i].poCodeList;
111
0
    }
112
113
4
    CPLFree(pasAttDesc);
114
4
    nAttCount = 0;
115
4
    pasAttDesc = nullptr;
116
117
4
    CPLFree(pszProduct);
118
4
    pszProduct = nullptr;
119
120
4
    CPLFree(pszPVName);
121
4
    pszPVName = nullptr;
122
123
4
    CPLFree(pszTileName);
124
4
    pszTileName = nullptr;
125
4
}
126
127
/************************************************************************/
128
/*                               Close()                                */
129
/*                                                                      */
130
/*      Close the file, but don't wipe out our knowledge about this     */
131
/*      file.                                                           */
132
/************************************************************************/
133
134
void NTFFileReader::Close()
135
136
4
{
137
4
    if (poSavedRecord != nullptr)
138
0
        delete poSavedRecord;
139
4
    poSavedRecord = nullptr;
140
141
4
    nPreSavedPos = nPostSavedPos = 0;
142
4
    nSavedFeatureId = nBaseFeatureId;
143
4
    if (fp != nullptr)
144
2
    {
145
2
        VSIFCloseL(fp);
146
2
        fp = nullptr;
147
2
    }
148
149
4
    CacheClean();
150
4
}
151
152
/************************************************************************/
153
/*                                Open()                                */
154
/************************************************************************/
155
156
int NTFFileReader::Open(const char *pszFilenameIn)
157
158
2
{
159
2
    if (pszFilenameIn != nullptr)
160
2
    {
161
2
        ClearDefs();
162
163
2
        CPLFree(pszFilename);
164
2
        pszFilename = CPLStrdup(pszFilenameIn);
165
2
    }
166
0
    else
167
0
        Close();
168
169
    /* -------------------------------------------------------------------- */
170
    /*      Open the file.                                                  */
171
    /* -------------------------------------------------------------------- */
172
2
    fp = VSIFOpenL(pszFilename, "rb");
173
174
    // notdef: we should likely issue a proper CPL error message based
175
    // based on errno here.
176
2
    if (fp == nullptr)
177
0
    {
178
0
        CPLError(CE_Failure, CPLE_OpenFailed,
179
0
                 "Unable to open file `%s' for read access.\n", pszFilename);
180
0
        return FALSE;
181
0
    }
182
183
    /* -------------------------------------------------------------------- */
184
    /*      If we are just reopening an existing file we will just scan     */
185
    /*      past the section header ... no need to reform all the definitions.*/
186
    /* -------------------------------------------------------------------- */
187
2
    if (pszFilenameIn == nullptr)
188
0
    {
189
0
        NTFRecord *poRecord = nullptr;
190
191
0
        for (poRecord = new NTFRecord(fp);
192
0
             poRecord->GetType() != NRT_VTR && poRecord->GetType() != NRT_SHR;
193
0
             poRecord = new NTFRecord(fp))
194
0
        {
195
0
            delete poRecord;
196
0
        }
197
198
0
        delete poRecord;
199
200
0
        return TRUE;
201
0
    }
202
203
    /* -------------------------------------------------------------------- */
204
    /*      Read the first record, and verify it is a proper volume header. */
205
    /* -------------------------------------------------------------------- */
206
2
    NTFRecord oVHR(fp);
207
208
2
    if (oVHR.GetType() != NRT_VHR)
209
0
    {
210
0
        CPLError(CE_Failure, CPLE_AppDefined,
211
0
                 "File `%s' appears to not be a UK NTF file.\n", pszFilename);
212
0
        return FALSE;
213
0
    }
214
215
2
    nNTFLevel = atoi(oVHR.GetField(57, 57));
216
2
    if (!(nNTFLevel >= 1 && nNTFLevel <= 5))
217
0
    {
218
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid value : nNTFLevel = %d",
219
0
                 nNTFLevel);
220
0
        return FALSE;
221
0
    }
222
223
    /* -------------------------------------------------------------------- */
224
    /*      Read records till we get the section header.                    */
225
    /* -------------------------------------------------------------------- */
226
2
    NTFRecord *poRecord = nullptr;
227
228
2
    for (poRecord = new NTFRecord(fp);
229
2
         poRecord->GetType() != NRT_VTR && poRecord->GetType() != NRT_SHR;
230
2
         poRecord = new NTFRecord(fp))
231
0
    {
232
        /* --------------------------------------------------------------------
233
         */
234
        /*      Handle feature class name records. */
235
        /* --------------------------------------------------------------------
236
         */
237
0
        if (poRecord->GetType() == NRT_FCR && poRecord->GetLength() >= 37)
238
0
        {
239
0
            nFCCount++;
240
241
0
            papszFCNum = CSLAddString(papszFCNum, poRecord->GetField(3, 6));
242
243
0
            CPLString osFCName;
244
0
            const char *pszData = poRecord->GetData();
245
246
            // CODE_COM
247
0
            int iChar = 15;
248
0
            for (; pszData[iChar] == ' ' && iChar > 5; iChar--)
249
0
            {
250
0
            }
251
252
0
            if (iChar > 6)
253
0
                osFCName += poRecord->GetField(7, iChar + 1);
254
255
            // STCLASS
256
0
            for (iChar = 35; pszData[iChar] == ' ' && iChar > 15; iChar--)
257
0
            {
258
0
            }
259
260
0
            if (iChar > 15)
261
0
            {
262
0
                if (!osFCName.empty())
263
0
                    osFCName += " : ";
264
0
                osFCName += poRecord->GetField(17, iChar + 1);
265
0
            }
266
267
            // FEATDES
268
0
            for (iChar = 36; pszData[iChar] != '\0' && pszData[iChar] != '\\';
269
0
                 iChar++)
270
0
            {
271
0
            }
272
273
0
            if (iChar > 37)
274
0
            {
275
0
                if (!osFCName.empty())
276
0
                    osFCName += " : ";
277
0
                osFCName += poRecord->GetField(37, iChar);
278
0
            }
279
280
0
            papszFCName = CSLAddString(papszFCName, osFCName);
281
0
        }
282
283
        /* --------------------------------------------------------------------
284
         */
285
        /*      Handle attribute description records. */
286
        /* --------------------------------------------------------------------
287
         */
288
0
        else if (poRecord->GetType() == NRT_ADR)
289
0
        {
290
0
            nAttCount++;
291
292
0
            pasAttDesc = static_cast<NTFAttDesc *>(
293
0
                CPLRealloc(pasAttDesc, sizeof(NTFAttDesc) * nAttCount));
294
0
            memset(&pasAttDesc[nAttCount - 1], 0, sizeof(NTFAttDesc));
295
296
0
            if (!ProcessAttDesc(poRecord, pasAttDesc + nAttCount - 1))
297
0
                nAttCount--;
298
0
        }
299
300
        /* --------------------------------------------------------------------
301
         */
302
        /*      Handle attribute description records. */
303
        /* --------------------------------------------------------------------
304
         */
305
0
        else if (poRecord->GetType() == NRT_CODELIST)
306
0
        {
307
0
            NTFCodeList *poCodeList = new NTFCodeList(poRecord);
308
0
            NTFAttDesc *psAttDesc = GetAttDesc(poCodeList->szValType);
309
0
            if (psAttDesc == nullptr)
310
0
            {
311
0
                CPLDebug("NTF", "Got CODELIST for %s without ATTDESC.",
312
0
                         poCodeList->szValType);
313
0
                delete poCodeList;
314
0
            }
315
0
            else if (psAttDesc->poCodeList != nullptr)
316
0
            {
317
                // Should not happen on sane files.
318
0
                delete poCodeList;
319
0
            }
320
0
            else
321
0
            {
322
0
                psAttDesc->poCodeList = poCodeList;
323
0
            }
324
0
        }
325
326
        /* --------------------------------------------------------------------
327
         */
328
        /*      Handle database header record. */
329
        /* --------------------------------------------------------------------
330
         */
331
0
        else if (poRecord->GetType() == NRT_DHR && pszProduct == nullptr)
332
0
        {
333
0
            pszProduct = CPLStrdup(poRecord->GetField(3, 22));
334
0
            for (int iChar = static_cast<int>(strlen(pszProduct)) - 1;
335
0
                 iChar > 0 && pszProduct[iChar] == ' ';
336
0
                 pszProduct[iChar--] = '\0')
337
0
            {
338
0
            }
339
340
0
            pszPVName = CPLStrdup(poRecord->GetField(76 + 3, 76 + 22));
341
0
            for (int iChar = static_cast<int>(strlen(pszPVName)) - 1;
342
0
                 iChar > 0 && pszPVName[iChar] == ' ';
343
0
                 pszPVName[iChar--] = '\0')
344
0
            {
345
0
            }
346
0
        }
347
348
0
        delete poRecord;
349
0
    }
350
351
    /* -------------------------------------------------------------------- */
352
    /*      Did we fall off the end without finding what we were looking    */
353
    /*      for?                                                            */
354
    /* -------------------------------------------------------------------- */
355
2
    if (poRecord->GetType() == NRT_VTR)
356
2
    {
357
2
        delete poRecord;
358
2
        CPLError(CE_Failure, CPLE_AppDefined,
359
2
                 "Could not find section header record in %s.\n", pszFilename);
360
2
        return FALSE;
361
2
    }
362
363
0
    if (pszProduct == nullptr)
364
0
    {
365
0
        delete poRecord;
366
0
        CPLError(CE_Failure, CPLE_AppDefined,
367
0
                 "Could not find product type in %s.\n", pszFilename);
368
0
        return FALSE;
369
0
    }
370
371
    /* -------------------------------------------------------------------- */
372
    /*      Classify the product type.                                      */
373
    /* -------------------------------------------------------------------- */
374
0
    if (STARTS_WITH_CI(pszProduct, "LAND-LINE") && strlen(pszPVName) > 5 &&
375
0
        CPLAtof(pszPVName + 5) < 1.3)
376
0
        nProduct = NPC_LANDLINE;
377
0
    else if (STARTS_WITH_CI(pszProduct, "LAND-LINE"))
378
0
        nProduct = NPC_LANDLINE99;
379
0
    else if (EQUAL(pszProduct, "OS_LANDRANGER_CONT"))  // Panorama
380
0
        nProduct = NPC_LANDRANGER_CONT;
381
0
    else if (EQUAL(pszProduct, "L-F_PROFILE_CON"))  // Panorama
382
0
        nProduct = NPC_LANDFORM_PROFILE_CONT;
383
0
    else if (STARTS_WITH_CI(pszProduct, "Strategi"))
384
0
        nProduct = NPC_STRATEGI;
385
0
    else if (STARTS_WITH_CI(pszProduct, "Meridian_02"))
386
0
        nProduct = NPC_MERIDIAN2;
387
0
    else if (STARTS_WITH_CI(pszProduct, "Meridian_01"))
388
0
        nProduct = NPC_MERIDIAN;
389
0
    else if (EQUAL(pszProduct, NTF_BOUNDARYLINE) &&
390
0
             STARTS_WITH_CI(pszPVName, "A10N_FC"))
391
0
        nProduct = NPC_BOUNDARYLINE;
392
0
    else if (EQUAL(pszProduct, NTF_BOUNDARYLINE) &&
393
0
             STARTS_WITH_CI(pszPVName, "A20N_FC"))
394
0
        nProduct = NPC_BL2000;
395
0
    else if (STARTS_WITH_CI(pszProduct, "BaseData.GB"))
396
0
        nProduct = NPC_BASEDATA;
397
0
    else if (STARTS_WITH_CI(pszProduct, "OSCAR_ASSET"))
398
0
        nProduct = NPC_OSCAR_ASSET;
399
0
    else if (STARTS_WITH_CI(pszProduct, "OSCAR_TRAFF"))
400
0
        nProduct = NPC_OSCAR_TRAFFIC;
401
0
    else if (STARTS_WITH_CI(pszProduct, "OSCAR_ROUTE"))
402
0
        nProduct = NPC_OSCAR_ROUTE;
403
0
    else if (STARTS_WITH_CI(pszProduct, "OSCAR_NETWO"))
404
0
        nProduct = NPC_OSCAR_NETWORK;
405
0
    else if (STARTS_WITH_CI(pszProduct, "ADDRESS_POI"))
406
0
        nProduct = NPC_ADDRESS_POINT;
407
0
    else if (STARTS_WITH_CI(pszProduct, "CODE_POINT"))
408
0
    {
409
0
        if (GetAttDesc("RH") == nullptr)
410
0
            nProduct = NPC_CODE_POINT;
411
0
        else
412
0
            nProduct = NPC_CODE_POINT_PLUS;
413
0
    }
414
0
    else if (STARTS_WITH_CI(pszProduct, "OS_LANDRANGER_DTM"))
415
0
        nProduct = NPC_LANDRANGER_DTM;
416
0
    else if (STARTS_WITH_CI(pszProduct, "L-F_PROFILE_DTM"))
417
0
        nProduct = NPC_LANDFORM_PROFILE_DTM;
418
0
    else if (STARTS_WITH_CI(pszProduct, "NEXTMap Britain DTM"))
419
0
        nProduct = NPC_LANDFORM_PROFILE_DTM;  // Treat as landform
420
421
0
    if (poDS->GetOption("FORCE_GENERIC") != nullptr &&
422
0
        !EQUAL(poDS->GetOption("FORCE_GENERIC"), "OFF"))
423
0
        nProduct = NPC_UNKNOWN;
424
425
    // No point in caching lines if there are no polygons.
426
0
    if (nProduct != NPC_BOUNDARYLINE && nProduct != NPC_BL2000)
427
0
        bCacheLines = FALSE;
428
429
    /* -------------------------------------------------------------------- */
430
    /*      Handle the section header record.                               */
431
    /* -------------------------------------------------------------------- */
432
0
    nSavedFeatureId = nBaseFeatureId;
433
0
    nStartPos = VSIFTellL(fp);
434
435
0
    pszTileName = CPLStrdup(poRecord->GetField(3, 12));  // SECT_REF
436
0
    size_t nTileNameLen = strlen(pszTileName);
437
0
    while (nTileNameLen > 0 && pszTileName[nTileNameLen - 1] == ' ')
438
0
    {
439
0
        pszTileName[nTileNameLen - 1] = '\0';
440
0
        nTileNameLen--;
441
0
    }
442
443
0
    nCoordWidth = atoi(poRecord->GetField(15, 19));  // XYLEN
444
0
    if (nCoordWidth <= 0)
445
0
        nCoordWidth = 10;
446
447
0
    nZWidth = atoi(poRecord->GetField(31, 35));  // ZLEN
448
0
    if (nZWidth <= 0)
449
0
        nZWidth = 10;
450
451
0
    dfXYMult = atoi(poRecord->GetField(21, 30)) / 1000.0;  // XY_MULT
452
0
    dfXOrigin = atoi(poRecord->GetField(47, 56));
453
0
    dfYOrigin = atoi(poRecord->GetField(57, 66));
454
0
    dfTileXSize = atoi(poRecord->GetField(23 + 74, 32 + 74));
455
0
    dfTileYSize = atoi(poRecord->GetField(33 + 74, 42 + 74));
456
0
    dfZMult = atoi(poRecord->GetField(37, 46)) / 1000.0;
457
458
    /* -------------------------------------------------------------------- */
459
    /*      Setup scale and transformation factor for text height.          */
460
    /* -------------------------------------------------------------------- */
461
0
    if (poRecord->GetLength() >= 187)
462
0
        dfScale = atoi(poRecord->GetField(148 + 31, 148 + 39));
463
0
    else if (nProduct == NPC_STRATEGI)
464
0
        dfScale = 250000;
465
0
    else if (nProduct == NPC_MERIDIAN || nProduct == NPC_MERIDIAN2)
466
0
        dfScale = 100000;
467
0
    else if (nProduct == NPC_LANDFORM_PROFILE_CONT)
468
0
        dfScale = 10000;
469
0
    else if (nProduct == NPC_LANDRANGER_CONT)
470
0
        dfScale = 50000;
471
0
    else if (nProduct == NPC_OSCAR_ASSET || nProduct == NPC_OSCAR_TRAFFIC ||
472
0
             nProduct == NPC_OSCAR_NETWORK || nProduct == NPC_OSCAR_ROUTE)
473
0
        dfScale = 10000;
474
0
    else if (nProduct == NPC_BASEDATA)
475
0
        dfScale = 625000;
476
0
    else /*if( nProduct == NPC_BOUNDARYLINE ) or default case */
477
0
        dfScale = 10000;
478
479
0
    if (dfScale != 0.0)
480
0
        dfPaperToGround = dfScale / 1000.0;
481
0
    else
482
0
        dfPaperToGround = 0.0;
483
484
0
    delete poRecord;
485
486
    /* -------------------------------------------------------------------- */
487
    /*      Ensure we have appropriate layers defined.                      */
488
    /* -------------------------------------------------------------------- */
489
0
    CPLErrorReset();
490
491
0
    if (!IsRasterProduct())
492
0
        EstablishLayers();
493
0
    else
494
0
        EstablishRasterAccess();
495
496
0
    return CPLGetLastErrorType() != CE_Failure;
497
0
}
498
499
/************************************************************************/
500
/*                            DumpReadable()                            */
501
/************************************************************************/
502
503
void NTFFileReader::DumpReadable(FILE *fpLog)
504
505
0
{
506
0
    fprintf(fpLog, "Tile Name = %s\n", pszTileName);
507
0
    fprintf(fpLog, "Product = %s\n", pszProduct);
508
0
    fprintf(fpLog, "NTFLevel = %d\n", nNTFLevel);
509
0
    fprintf(fpLog, "XYLEN = %d\n", nCoordWidth);
510
0
    fprintf(fpLog, "XY_MULT = %g\n", dfXYMult);
511
0
    fprintf(fpLog, "X_ORIG = %g\n", dfXOrigin);
512
0
    fprintf(fpLog, "Y_ORIG = %g\n", dfYOrigin);
513
0
    fprintf(fpLog, "XMAX = %g\n", dfTileXSize);
514
0
    fprintf(fpLog, "YMAX = %g\n", dfTileYSize);
515
0
}
516
517
/************************************************************************/
518
/*                          ProcessGeometry()                           */
519
/*                                                                      */
520
/*      Drop duplicate vertices from line strings ... they mess up      */
521
/*      FME's polygon handling sometimes.                               */
522
/************************************************************************/
523
524
OGRGeometry *NTFFileReader::ProcessGeometry(NTFRecord *poRecord, int *pnGeomId)
525
526
0
{
527
0
    if (poRecord->GetType() == NRT_GEOMETRY3D)
528
0
        return ProcessGeometry3D(poRecord, pnGeomId);
529
530
0
    else if (poRecord->GetType() != NRT_GEOMETRY)
531
0
        return nullptr;
532
533
0
    const int nGType = atoi(poRecord->GetField(9, 9));       // GTYPE
534
0
    const int nNumCoord = atoi(poRecord->GetField(10, 13));  // NUM_COORD
535
0
    if (nNumCoord < 0)
536
0
        return nullptr;
537
0
    if (pnGeomId != nullptr)
538
0
        *pnGeomId = atoi(poRecord->GetField(3, 8));  // GEOM_ID
539
540
    /* -------------------------------------------------------------------- */
541
    /*      Point                                                           */
542
    /* -------------------------------------------------------------------- */
543
0
    OGRGeometry *poGeometry = nullptr;
544
0
    if (nGType == 1)
545
0
    {
546
0
        const double dfX =
547
0
            atoi(poRecord->GetField(14, 14 + GetXYLen() - 1)) * GetXYMult() +
548
0
            GetXOrigin();
549
0
        const double dfY =
550
0
            atoi(poRecord->GetField(14 + GetXYLen(), 14 + GetXYLen() * 2 - 1)) *
551
0
                GetXYMult() +
552
0
            GetYOrigin();
553
554
0
        poGeometry = new OGRPoint(dfX, dfY);
555
0
    }
556
557
    /* -------------------------------------------------------------------- */
558
    /*      Line (or arc)                                                   */
559
    /* -------------------------------------------------------------------- */
560
0
    else if (nGType == 2 || nGType == 3 || nGType == 4)
561
0
    {
562
563
0
        if (nNumCoord > 0 && poRecord->GetLength() <
564
0
                                 14 + (nNumCoord - 1) * (GetXYLen() * 2 + 1) +
565
0
                                     GetXYLen() * 2 - 1)
566
0
        {
567
0
            return nullptr;
568
0
        }
569
570
0
        OGRLineString *poLine = new OGRLineString;
571
0
        double dfXLast = 0.0;
572
0
        double dfYLast = 0.0;
573
0
        int nOutCount = 0;
574
575
0
        poGeometry = poLine;
576
0
        poLine->setNumPoints(nNumCoord);
577
0
        for (int iCoord = 0; iCoord < nNumCoord; iCoord++)
578
0
        {
579
0
            const int iStart = 14 + iCoord * (GetXYLen() * 2 + 1);
580
581
0
            const double dfX =
582
0
                atoi(poRecord->GetField(iStart + 0, iStart + GetXYLen() - 1)) *
583
0
                    GetXYMult() +
584
0
                GetXOrigin();
585
0
            const double dfY =
586
0
                atoi(poRecord->GetField(iStart + GetXYLen(),
587
0
                                        iStart + GetXYLen() * 2 - 1)) *
588
0
                    GetXYMult() +
589
0
                GetYOrigin();
590
591
0
            if (iCoord == 0)
592
0
            {
593
0
                dfXLast = dfX;
594
0
                dfYLast = dfY;
595
0
                poLine->setPoint(nOutCount++, dfX, dfY);
596
0
            }
597
0
            else if (dfXLast != dfX || dfYLast != dfY)
598
0
            {
599
0
                dfXLast = dfX;
600
0
                dfYLast = dfY;
601
0
                poLine->setPoint(nOutCount++, dfX, dfY);
602
0
            }
603
0
        }
604
0
        poLine->setNumPoints(nOutCount);
605
606
0
        CacheAddByGeomId(atoi(poRecord->GetField(3, 8)), poLine);
607
0
    }
608
609
    /* -------------------------------------------------------------------- */
610
    /*      Arc defined by three points on the arc.                         */
611
    /* -------------------------------------------------------------------- */
612
0
    else if (nGType == 5 && nNumCoord == 3)
613
0
    {
614
0
        double adfX[3] = {0.0, 0.0, 0.0};
615
0
        double adfY[3] = {0.0, 0.0, 0.0};
616
617
0
        for (int iCoord = 0; iCoord < nNumCoord; iCoord++)
618
0
        {
619
0
            const int iStart = 14 + iCoord * (GetXYLen() * 2 + 1);
620
621
0
            adfX[iCoord] =
622
0
                atoi(poRecord->GetField(iStart + 0, iStart + GetXYLen() - 1)) *
623
0
                    GetXYMult() +
624
0
                GetXOrigin();
625
0
            adfY[iCoord] =
626
0
                atoi(poRecord->GetField(iStart + GetXYLen(),
627
0
                                        iStart + GetXYLen() * 2 - 1)) *
628
0
                    GetXYMult() +
629
0
                GetYOrigin();
630
0
        }
631
632
0
        poGeometry = NTFStrokeArcToOGRGeometry_Points(
633
0
            adfX[0], adfY[0], adfX[1], adfY[1], adfX[2], adfY[2], 72);
634
0
    }
635
636
    /* -------------------------------------------------------------------- */
637
    /*      Circle                                                          */
638
    /* -------------------------------------------------------------------- */
639
0
    else if (nGType == 7)
640
0
    {
641
0
        const int iCenterStart = 14;
642
0
        const int iArcStart = 14 + 2 * GetXYLen() + 1;
643
644
0
        const double dfCenterX =
645
0
            atoi(poRecord->GetField(iCenterStart,
646
0
                                    iCenterStart + GetXYLen() - 1)) *
647
0
                GetXYMult() +
648
0
            GetXOrigin();
649
0
        const double dfCenterY =
650
0
            atoi(poRecord->GetField(iCenterStart + GetXYLen(),
651
0
                                    iCenterStart + GetXYLen() * 2 - 1)) *
652
0
                GetXYMult() +
653
0
            GetYOrigin();
654
655
0
        const double dfArcX =
656
0
            atoi(poRecord->GetField(iArcStart, iArcStart + GetXYLen() - 1)) *
657
0
                GetXYMult() +
658
0
            GetXOrigin();
659
0
        const double dfArcY =
660
0
            atoi(poRecord->GetField(iArcStart + GetXYLen(),
661
0
                                    iArcStart + GetXYLen() * 2 - 1)) *
662
0
                GetXYMult() +
663
0
            GetYOrigin();
664
665
0
        const double dfRadius =
666
0
            sqrt((dfCenterX - dfArcX) * (dfCenterX - dfArcX) +
667
0
                 (dfCenterY - dfArcY) * (dfCenterY - dfArcY));
668
669
0
        poGeometry = NTFStrokeArcToOGRGeometry_Angles(dfCenterX, dfCenterY,
670
0
                                                      dfRadius, 0.0, 360.0, 72);
671
0
    }
672
673
0
    else
674
0
    {
675
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unhandled GType = %d", nGType);
676
0
    }
677
678
0
    if (poGeometry != nullptr)
679
0
        poGeometry->assignSpatialReference(poDS->DSGetSpatialRef());
680
681
0
    return poGeometry;
682
0
}
683
684
/************************************************************************/
685
/*                         ProcessGeometry3D()                          */
686
/************************************************************************/
687
688
OGRGeometry *NTFFileReader::ProcessGeometry3D(NTFRecord *poRecord,
689
                                              int *pnGeomId)
690
691
0
{
692
0
    OGRGeometry *poGeometry = nullptr;
693
694
0
    if (poRecord->GetType() != NRT_GEOMETRY3D)
695
0
        return nullptr;
696
697
0
    const int nGType = atoi(poRecord->GetField(9, 9));       // GTYPE
698
0
    const int nNumCoord = atoi(poRecord->GetField(10, 13));  // NUM_COORD
699
0
    if (pnGeomId != nullptr)
700
0
        *pnGeomId = atoi(poRecord->GetField(3, 8));  // GEOM_ID
701
702
0
    if (nGType == 1)
703
0
    {
704
0
        if (14 + 1 + 2 * static_cast<GIntBig>(GetXYLen()) + nZWidth - 1 >
705
0
            INT_MAX)
706
0
        {
707
0
            return nullptr;
708
0
        }
709
0
        const double dfX =
710
0
            atoi(poRecord->GetField(14, 14 + GetXYLen() - 1)) * GetXYMult() +
711
0
            GetXOrigin();
712
0
        const double dfY =
713
0
            atoi(poRecord->GetField(14 + GetXYLen(), 14 + GetXYLen() * 2 - 1)) *
714
0
                GetXYMult() +
715
0
            GetYOrigin();
716
0
        const double dfZ =
717
0
            atoi(poRecord->GetField(14 + 1 + 2 * GetXYLen(),
718
0
                                    14 + 1 + 2 * GetXYLen() + nZWidth - 1)) *
719
0
            dfZMult;
720
721
0
        poGeometry = new OGRPoint(dfX, dfY, dfZ);
722
0
    }
723
724
0
    else if (nGType == 2)
725
0
    {
726
0
        if (nNumCoord < 0 || 14 +
727
0
                                     static_cast<GIntBig>(nNumCoord - 1) *
728
0
                                         (GetXYLen() * 2 + nZWidth + 2) +
729
0
                                     1 + 2 * GetXYLen() + nZWidth - 1 >
730
0
                                 INT_MAX)
731
0
        {
732
0
            return nullptr;
733
0
        }
734
735
0
        OGRLineString *poLine = new OGRLineString;
736
0
        double dfXLast = 0.0;
737
0
        double dfYLast = 0.0;
738
0
        int nOutCount = 0;
739
740
0
        poGeometry = poLine;
741
0
        poLine->setNumPoints(nNumCoord);
742
0
        const GUInt32 nErrorsBefore = CPLGetErrorCounter();
743
0
        for (int iCoord = 0; iCoord < nNumCoord; iCoord++)
744
0
        {
745
0
            const int iStart = 14 + iCoord * (GetXYLen() * 2 + nZWidth + 2);
746
747
0
            const char *pszX =
748
0
                poRecord->GetField(iStart + 0, iStart + GetXYLen() - 1);
749
0
            bool bSpace = pszX[0] == ' ';
750
0
            const double dfX = atoi(pszX) * GetXYMult() + GetXOrigin();
751
0
            const char *pszY = poRecord->GetField(iStart + GetXYLen(),
752
0
                                                  iStart + GetXYLen() * 2 - 1);
753
0
            bSpace |= pszY[0] == ' ';
754
0
            const double dfY = atoi(pszY) * GetXYMult() + GetYOrigin();
755
756
0
            const char *pszZ =
757
0
                poRecord->GetField(iStart + 1 + 2 * GetXYLen(),
758
0
                                   iStart + 1 + 2 * GetXYLen() + nZWidth - 1);
759
0
            bSpace |= pszZ[0] == ' ';
760
0
            const double dfZ = atoi(pszZ) * dfZMult;
761
0
            if (bSpace && CPLGetErrorCounter() != nErrorsBefore)
762
0
            {
763
0
                delete poGeometry;
764
0
                return nullptr;
765
0
            }
766
767
0
            if (iCoord == 0)
768
0
            {
769
0
                dfXLast = dfX;
770
0
                dfYLast = dfY;
771
0
                poLine->setPoint(nOutCount++, dfX, dfY, dfZ);
772
0
            }
773
0
            else if (dfXLast != dfX || dfYLast != dfY)
774
0
            {
775
0
                dfXLast = dfX;
776
0
                dfYLast = dfY;
777
0
                poLine->setPoint(nOutCount++, dfX, dfY, dfZ);
778
0
            }
779
0
        }
780
0
        poLine->setNumPoints(nOutCount);
781
782
0
        CacheAddByGeomId(atoi(poRecord->GetField(3, 8)), poLine);
783
0
    }
784
785
0
    if (poGeometry != nullptr)
786
0
        poGeometry->assignSpatialReference(poDS->DSGetSpatialRef());
787
788
0
    return poGeometry;
789
0
}
790
791
/************************************************************************/
792
/*                           ProcessAttDesc()                           */
793
/************************************************************************/
794
795
int NTFFileReader::ProcessAttDesc(NTFRecord *poRecord, NTFAttDesc *psAD)
796
797
0
{
798
0
    psAD->poCodeList = nullptr;
799
0
    if (poRecord->GetType() != NRT_ADR || poRecord->GetLength() < 13)
800
0
        return FALSE;
801
802
0
    snprintf(psAD->val_type, sizeof(psAD->val_type), "%s",
803
0
             poRecord->GetField(3, 4));
804
0
    snprintf(psAD->fwidth, sizeof(psAD->fwidth), "%s",
805
0
             poRecord->GetField(5, 7));
806
0
    snprintf(psAD->finter, sizeof(psAD->finter), "%s",
807
0
             poRecord->GetField(8, 12));
808
809
0
    const char *pszData = poRecord->GetData();
810
0
    int iChar = 12;  // Used after for.
811
0
    for (; pszData[iChar] != '\0' && pszData[iChar] != '\\'; iChar++)
812
0
    {
813
0
    }
814
815
0
    snprintf(psAD->att_name, sizeof(psAD->att_name), "%s",
816
0
             poRecord->GetField(13, iChar));
817
818
0
    return TRUE;
819
0
}
820
821
/************************************************************************/
822
/*                         ProcessAttRecGroup()                         */
823
/*                                                                      */
824
/*      Extract attribute values from all attribute records in a        */
825
/*      record set.                                                     */
826
/************************************************************************/
827
828
int NTFFileReader::ProcessAttRecGroup(NTFRecord **papoRecords,
829
                                      char ***ppapszTypes, char ***ppapszValues)
830
831
0
{
832
0
    *ppapszTypes = nullptr;
833
0
    *ppapszValues = nullptr;
834
835
0
    for (int iRec = 0; papoRecords[iRec] != nullptr; iRec++)
836
0
    {
837
0
        if (papoRecords[iRec]->GetType() != NRT_ATTREC)
838
0
            continue;
839
840
0
        char **papszTypes1 = nullptr;
841
0
        char **papszValues1 = nullptr;
842
0
        if (!ProcessAttRec(papoRecords[iRec], nullptr, &papszTypes1,
843
0
                           &papszValues1))
844
0
        {
845
0
            CSLDestroy(*ppapszTypes);
846
0
            CSLDestroy(*ppapszValues);
847
0
            *ppapszTypes = nullptr;
848
0
            *ppapszValues = nullptr;
849
0
            return FALSE;
850
0
        }
851
852
0
        if (*ppapszTypes == nullptr)
853
0
        {
854
0
            *ppapszTypes = papszTypes1;
855
0
            *ppapszValues = papszValues1;
856
0
        }
857
0
        else
858
0
        {
859
0
            for (int i = 0; papszTypes1[i] != nullptr; i++)
860
0
            {
861
0
                *ppapszTypes = CSLAddString(*ppapszTypes, papszTypes1[i]);
862
0
                *ppapszValues = CSLAddString(*ppapszValues, papszValues1[i]);
863
0
            }
864
0
            CSLDestroy(papszTypes1);
865
0
            CSLDestroy(papszValues1);
866
0
        }
867
0
    }
868
869
0
    return TRUE;
870
0
}
871
872
/************************************************************************/
873
/*                           ProcessAttRec()                            */
874
/************************************************************************/
875
876
int NTFFileReader::ProcessAttRec(NTFRecord *poRecord, int *pnAttId,
877
                                 char ***ppapszTypes, char ***ppapszValues)
878
879
0
{
880
0
    if (pnAttId != nullptr)
881
0
        *pnAttId = 0;
882
0
    *ppapszTypes = nullptr;
883
0
    *ppapszValues = nullptr;
884
885
0
    if (poRecord->GetType() != NRT_ATTREC || poRecord->GetLength() < 8)
886
0
        return FALSE;
887
888
    /* -------------------------------------------------------------------- */
889
    /*      Extract the attribute id.                                       */
890
    /* -------------------------------------------------------------------- */
891
0
    if (pnAttId != nullptr)
892
0
        *pnAttId = atoi(poRecord->GetField(3, 8));
893
894
    /* ==================================================================== */
895
    /*      Loop handling attribute till we get a '0' indicating the end    */
896
    /*      of the record.                                                  */
897
    /* ==================================================================== */
898
899
0
    int iOffset = 8;
900
0
    const char *pszData = poRecord->GetData();
901
0
    bool bError = false;
902
903
0
    while (iOffset < poRecord->GetLength() && pszData[iOffset] != DIGIT_ZERO)
904
0
    {
905
        /* --------------------------------------------------------------------
906
         */
907
        /*      Extract the two letter code name for the attribute, and use */
908
        /*      it to find the correct ATTDESC info. */
909
        /* --------------------------------------------------------------------
910
         */
911
0
        NTFAttDesc *psAttDesc = GetAttDesc(pszData + iOffset);
912
0
        if (psAttDesc == nullptr)
913
0
        {
914
0
            CPLDebug("NTF", "Couldn't translate attrec type `%2.2s'.",
915
0
                     pszData + iOffset);
916
0
            bError = true;
917
0
            break;
918
0
        }
919
920
0
        *ppapszTypes = CSLAddString(
921
0
            *ppapszTypes, poRecord->GetField(iOffset + 1, iOffset + 2));
922
923
        /* --------------------------------------------------------------------
924
         */
925
        /*      Establish the width of the value.  Zero width fields are */
926
        /*      terminated by a backslash. */
927
        /* --------------------------------------------------------------------
928
         */
929
0
        const int nFWidth = atoi(psAttDesc->fwidth);
930
0
        if (nFWidth < 0)
931
0
        {
932
0
            bError = true;
933
0
            break;
934
0
        }
935
0
        int nEnd = 0;
936
0
        if (nFWidth == 0)
937
0
        {
938
0
            const char *pszData2 = poRecord->GetData();
939
0
            if (iOffset + 2 >= poRecord->GetLength())
940
0
            {
941
0
                bError = true;
942
0
                break;
943
0
            }
944
0
            for (nEnd = iOffset + 2;
945
0
                 pszData2[nEnd] != '\\' && pszData2[nEnd] != '\0'; nEnd++)
946
0
            {
947
0
            }
948
0
        }
949
0
        else
950
0
        {
951
0
            nEnd = iOffset + 3 + nFWidth - 1;
952
0
        }
953
954
        /* --------------------------------------------------------------------
955
         */
956
        /*      Extract the value.  If it is formatted as fixed point real */
957
        /*      we reprocess it to insert the decimal point. */
958
        /* --------------------------------------------------------------------
959
         */
960
0
        const char *pszRawValue = poRecord->GetField(iOffset + 3, nEnd);
961
0
        *ppapszValues = CSLAddString(*ppapszValues, pszRawValue);
962
963
        /* --------------------------------------------------------------------
964
         */
965
        /*      Establish new offset position. */
966
        /* --------------------------------------------------------------------
967
         */
968
0
        if (nFWidth == 0)
969
0
        {
970
0
            iOffset = nEnd;
971
0
            if (iOffset >= poRecord->GetLength())
972
0
            {
973
0
                bError = (iOffset > poRecord->GetLength());
974
0
                break;
975
0
            }
976
0
            if (pszData[iOffset] == '\\')
977
0
                iOffset++;
978
0
        }
979
0
        else
980
0
            iOffset += 2 + nFWidth;
981
0
    }
982
983
0
    if (bError)
984
0
    {
985
0
        CSLDestroy(*ppapszTypes);
986
0
        CSLDestroy(*ppapszValues);
987
0
        *ppapszTypes = nullptr;
988
0
        *ppapszValues = nullptr;
989
0
    }
990
991
0
    return (*ppapszTypes != nullptr);
992
0
}
993
994
/************************************************************************/
995
/*                             GetAttDesc()                             */
996
/************************************************************************/
997
998
NTFAttDesc *NTFFileReader::GetAttDesc(const char *pszType)
999
1000
0
{
1001
0
    for (int i = 0; i < nAttCount; i++)
1002
0
    {
1003
0
        if (EQUALN(pszType, pasAttDesc[i].val_type, 2))
1004
0
            return pasAttDesc + i;
1005
0
    }
1006
1007
0
    return nullptr;
1008
0
}
1009
1010
/************************************************************************/
1011
/*                          ProcessAttValue()                           */
1012
/*                                                                      */
1013
/*      Take an attribute type/value pair and transform into a          */
1014
/*      meaningful attribute name, and value.  The source can be an     */
1015
/*      ATTREC or the VAL_TYPE/VALUE pair of a POINTREC or LINEREC.     */
1016
/*      The name is transformed from the two character short form to    */
1017
/*      the long user name.  The value will be transformed from         */
1018
/*      fixed point (with the decimal implicit) to fixed point with     */
1019
/*      an explicit decimal point if it has a "R" format.               */
1020
/*      Note: the returned *ppszAttValue has a very short lifetime      */
1021
/*      and should immediately be used. Further calls to                */
1022
/*      ProcessAttValue or CPLSPrintf() will invalidate it.             */
1023
/************************************************************************/
1024
1025
int NTFFileReader::ProcessAttValue(const char *pszValType,
1026
                                   const char *pszRawValue,
1027
                                   const char **ppszAttName,
1028
                                   const char **ppszAttValue,
1029
                                   const char **ppszCodeDesc)
1030
1031
0
{
1032
    /* -------------------------------------------------------------------- */
1033
    /*      Find the ATTDESC for this attribute, and assign return name value.*/
1034
    /* -------------------------------------------------------------------- */
1035
0
    NTFAttDesc *psAttDesc = GetAttDesc(pszValType);
1036
1037
0
    if (psAttDesc == nullptr)
1038
0
        return FALSE;
1039
1040
0
    if (ppszAttName != nullptr)
1041
0
        *ppszAttName = psAttDesc->att_name;
1042
1043
    /* -------------------------------------------------------------------- */
1044
    /*      Extract the value.  If it is formatted as fixed point real      */
1045
    /*      we reprocess it to insert the decimal point.                    */
1046
    /* -------------------------------------------------------------------- */
1047
0
    if (psAttDesc->finter[0] == 'R')
1048
0
    {
1049
0
        const char *pszDecimalPortion = nullptr;  // Used after for.
1050
1051
0
        for (pszDecimalPortion = psAttDesc->finter;
1052
0
             *pszDecimalPortion != ',' && *pszDecimalPortion != '\0';
1053
0
             pszDecimalPortion++)
1054
0
        {
1055
0
        }
1056
0
        if (*pszDecimalPortion == '\0')
1057
0
        {
1058
0
            *ppszAttValue = "";
1059
0
        }
1060
0
        else
1061
0
        {
1062
0
            const int nWidth = static_cast<int>(strlen(pszRawValue));
1063
0
            const int nPrecision = atoi(pszDecimalPortion + 1);
1064
0
            if (nPrecision < 0 || nPrecision >= nWidth)
1065
0
            {
1066
0
                *ppszAttValue = "";
1067
0
            }
1068
0
            else
1069
0
            {
1070
0
                CPLString osResult(pszRawValue);
1071
0
                osResult.resize(nWidth - nPrecision);
1072
0
                osResult += ".";
1073
0
                osResult += pszRawValue + nWidth - nPrecision;
1074
1075
0
                *ppszAttValue = CPLSPrintf("%s", osResult.c_str());
1076
0
            }
1077
0
        }
1078
0
    }
1079
1080
    /* -------------------------------------------------------------------- */
1081
    /*      If it is an integer, we just reformat to get rid of leading     */
1082
    /*      zeros.                                                          */
1083
    /* -------------------------------------------------------------------- */
1084
0
    else if (psAttDesc->finter[0] == 'I')
1085
0
    {
1086
0
        *ppszAttValue = CPLSPrintf("%d", atoi(pszRawValue));
1087
0
    }
1088
1089
    /* -------------------------------------------------------------------- */
1090
    /*      Otherwise we take the value directly.                           */
1091
    /* -------------------------------------------------------------------- */
1092
0
    else
1093
0
    {
1094
0
        *ppszAttValue = pszRawValue;
1095
0
    }
1096
1097
    /* -------------------------------------------------------------------- */
1098
    /*      Handle processing code values into code descriptions, if        */
1099
    /*      applicable.                                                     */
1100
    /* -------------------------------------------------------------------- */
1101
0
    if (ppszCodeDesc == nullptr)
1102
0
    {
1103
0
    }
1104
0
    else if (psAttDesc->poCodeList != nullptr)
1105
0
    {
1106
0
        *ppszCodeDesc = psAttDesc->poCodeList->Lookup(*ppszAttValue);
1107
0
    }
1108
0
    else
1109
0
    {
1110
0
        *ppszCodeDesc = nullptr;
1111
0
    }
1112
1113
0
    return TRUE;
1114
0
}
1115
1116
/************************************************************************/
1117
/*                        ApplyAttributeValues()                        */
1118
/*                                                                      */
1119
/*      Apply a series of attribute values to a feature from generic    */
1120
/*      attribute records.                                              */
1121
/************************************************************************/
1122
1123
void NTFFileReader::ApplyAttributeValues(OGRFeature *poFeature,
1124
                                         NTFRecord **papoGroup, ...)
1125
1126
0
{
1127
    /* -------------------------------------------------------------------- */
1128
    /*      Extract attribute values from record group.                     */
1129
    /* -------------------------------------------------------------------- */
1130
0
    char **papszTypes = nullptr;
1131
0
    char **papszValues = nullptr;
1132
1133
0
    if (!ProcessAttRecGroup(papoGroup, &papszTypes, &papszValues))
1134
0
        return;
1135
1136
    /* -------------------------------------------------------------------- */
1137
    /*      Handle attribute pairs                                          */
1138
    /* -------------------------------------------------------------------- */
1139
0
    va_list hVaArgs;
1140
1141
0
    va_start(hVaArgs, papoGroup);
1142
1143
0
    const char *pszAttName = nullptr;
1144
0
    while ((pszAttName = va_arg(hVaArgs, const char *)) != nullptr)
1145
0
    {
1146
0
        const int iField = va_arg(hVaArgs, int);
1147
1148
0
        ApplyAttributeValue(poFeature, iField, pszAttName, papszTypes,
1149
0
                            papszValues);
1150
0
    }
1151
1152
0
    va_end(hVaArgs);
1153
1154
    /* -------------------------------------------------------------------- */
1155
    /*      Cleanup.                                                        */
1156
    /* -------------------------------------------------------------------- */
1157
0
    CSLDestroy(papszTypes);
1158
0
    CSLDestroy(papszValues);
1159
0
}
1160
1161
/************************************************************************/
1162
/*                        ApplyAttributeValue()                         */
1163
/*                                                                      */
1164
/*      Apply the indicated attribute value to an OGRFeature field      */
1165
/*      if it exists in the attribute value list given.                 */
1166
/************************************************************************/
1167
1168
int NTFFileReader::ApplyAttributeValue(OGRFeature *poFeature, int iField,
1169
                                       const char *pszAttName,
1170
                                       char **papszTypes, char **papszValues)
1171
1172
0
{
1173
    /* -------------------------------------------------------------------- */
1174
    /*      Find the requested attribute in the name/value pair             */
1175
    /*      provided.  If not found that's fine, just return with           */
1176
    /*      notification.                                                   */
1177
    /* -------------------------------------------------------------------- */
1178
0
    const int iValue = CSLFindString(papszTypes, pszAttName);
1179
0
    if (iValue < 0)
1180
0
        return FALSE;
1181
1182
0
    CPLAssert(papszValues != nullptr);
1183
    /* -------------------------------------------------------------------- */
1184
    /*      Process the attribute value ... this really only has a          */
1185
    /*      useful effect for real numbers.                                 */
1186
    /* -------------------------------------------------------------------- */
1187
0
    const char *pszAttLongName = nullptr;
1188
0
    const char *pszAttValue = nullptr;
1189
0
    const char *pszCodeDesc = nullptr;
1190
1191
0
    if (!ProcessAttValue(pszAttName, papszValues[iValue], &pszAttLongName,
1192
0
                         &pszAttValue, &pszCodeDesc))
1193
0
        return FALSE;
1194
1195
    /* -------------------------------------------------------------------- */
1196
    /*      Apply the value to the field using the simple set string        */
1197
    /*      method.  Leave it to the OGRFeature::SetField() method to       */
1198
    /*      take care of translation to other types.                        */
1199
    /* -------------------------------------------------------------------- */
1200
0
    poFeature->SetField(iField, pszAttValue);
1201
1202
    /* -------------------------------------------------------------------- */
1203
    /*      Apply the code description if we found one.                     */
1204
    /* -------------------------------------------------------------------- */
1205
0
    if (pszCodeDesc != nullptr)
1206
0
    {
1207
0
        char szDescFieldName[256];
1208
1209
0
        snprintf(szDescFieldName, sizeof(szDescFieldName), "%s_DESC",
1210
0
                 poFeature->GetDefnRef()->GetFieldDefn(iField)->GetNameRef());
1211
0
        poFeature->SetField(szDescFieldName, pszCodeDesc);
1212
0
    }
1213
1214
0
    return TRUE;
1215
0
}
1216
1217
/************************************************************************/
1218
/*                             SaveRecord()                             */
1219
/************************************************************************/
1220
1221
void NTFFileReader::SaveRecord(NTFRecord *poRecord)
1222
1223
0
{
1224
0
    CPLAssert(poSavedRecord == nullptr);
1225
0
    poSavedRecord = poRecord;
1226
0
}
1227
1228
/************************************************************************/
1229
/*                             ReadRecord()                             */
1230
/************************************************************************/
1231
1232
NTFRecord *NTFFileReader::ReadRecord()
1233
1234
0
{
1235
0
    if (poSavedRecord != nullptr)
1236
0
    {
1237
0
        NTFRecord *poReturn = poSavedRecord;
1238
1239
0
        poSavedRecord = nullptr;
1240
1241
0
        return poReturn;
1242
0
    }
1243
0
    else
1244
0
    {
1245
0
        CPLErrorReset();
1246
0
        if (fp != nullptr)
1247
0
            nPreSavedPos = VSIFTellL(fp);
1248
0
        NTFRecord *poRecord = new NTFRecord(fp);
1249
0
        if (fp != nullptr)
1250
0
            nPostSavedPos = VSIFTellL(fp);
1251
1252
        /* ensure termination if we fail to read a record */
1253
0
        if (CPLGetLastErrorType() == CE_Failure)
1254
0
        {
1255
0
            delete poRecord;
1256
0
            poRecord = nullptr;
1257
0
        }
1258
1259
0
        return poRecord;
1260
0
    }
1261
0
}
1262
1263
/************************************************************************/
1264
/*                              GetFPPos()                              */
1265
/*                                                                      */
1266
/*      Return the current file pointer position.                       */
1267
/************************************************************************/
1268
1269
void NTFFileReader::GetFPPos(vsi_l_offset *pnPos, long *pnFID)
1270
1271
0
{
1272
0
    if (poSavedRecord != nullptr)
1273
0
        *pnPos = nPreSavedPos;
1274
0
    else
1275
0
        *pnPos = nPostSavedPos;
1276
1277
0
    if (pnFID != nullptr)
1278
0
        *pnFID = nSavedFeatureId;
1279
0
}
1280
1281
/************************************************************************/
1282
/*                              SetFPPos()                              */
1283
/************************************************************************/
1284
1285
int NTFFileReader::SetFPPos(vsi_l_offset nNewPos, long nNewFID)
1286
1287
0
{
1288
0
    if (nNewFID == nSavedFeatureId)
1289
0
        return TRUE;
1290
1291
0
    if (poSavedRecord != nullptr)
1292
0
    {
1293
0
        delete poSavedRecord;
1294
0
        poSavedRecord = nullptr;
1295
0
    }
1296
1297
0
    if (fp != nullptr && VSIFSeekL(fp, nNewPos, SEEK_SET) == 0)
1298
0
    {
1299
0
        nPreSavedPos = nPostSavedPos = nNewPos;
1300
0
        nSavedFeatureId = nNewFID;
1301
0
        return TRUE;
1302
0
    }
1303
0
    else
1304
0
        return FALSE;
1305
0
}
1306
1307
/************************************************************************/
1308
/*                               Reset()                                */
1309
/*                                                                      */
1310
/*      Reset reading to the first feature record.                      */
1311
/************************************************************************/
1312
1313
void NTFFileReader::Reset()
1314
1315
0
{
1316
0
    SetFPPos(nStartPos, nBaseFeatureId);
1317
0
    ClearCGroup();
1318
0
}
1319
1320
/************************************************************************/
1321
/*                            ClearCGroup()                             */
1322
/*                                                                      */
1323
/*      Clear the currently loaded record group.                        */
1324
/************************************************************************/
1325
1326
void NTFFileReader::ClearCGroup()
1327
1328
4
{
1329
4
    for (int i = 0; apoCGroup[i] != nullptr; i++)
1330
0
        delete apoCGroup[i];
1331
1332
4
    apoCGroup[0] = nullptr;
1333
4
    apoCGroup[1] = nullptr;
1334
4
}
1335
1336
/************************************************************************/
1337
/*                      DefaultNTFRecordGrouper()                       */
1338
/*                                                                      */
1339
/*      Default rules for figuring out if a new candidate record        */
1340
/*      belongs to a group of records that together form a feature      */
1341
/*      (a record group).                                               */
1342
/************************************************************************/
1343
1344
int DefaultNTFRecordGrouper(NTFFileReader *, NTFRecord **papoGroup,
1345
                            NTFRecord *poCandidate)
1346
1347
0
{
1348
    /* -------------------------------------------------------------------- */
1349
    /*      Is this group going to be a CPOLY set?  We can recognise        */
1350
    /*      this because we get repeating POLY/CHAIN sets without an        */
1351
    /*      intermediate attribute record.  This is a rather special case!  */
1352
    /* -------------------------------------------------------------------- */
1353
0
    if (papoGroup[0] != nullptr && papoGroup[1] != nullptr &&
1354
0
        papoGroup[0]->GetType() == NRT_POLYGON &&
1355
0
        papoGroup[1]->GetType() == NRT_CHAIN)
1356
0
    {
1357
        // We keep going till we get the seed geometry.
1358
0
        int iRec, bGotCPOLY = FALSE;
1359
1360
0
        for (iRec = 0; papoGroup[iRec] != nullptr; iRec++)
1361
0
        {
1362
0
            if (papoGroup[iRec]->GetType() == NRT_CPOLY)
1363
0
                bGotCPOLY = TRUE;
1364
0
        }
1365
1366
0
        if (bGotCPOLY && poCandidate->GetType() != NRT_GEOMETRY &&
1367
0
            poCandidate->GetType() != NRT_ATTREC)
1368
0
            return FALSE;
1369
1370
        /*
1371
         * this logic assumes we always get a point geometry with a CPOLY
1372
         * but that isn't always true, for instance with BL2000 data.  The
1373
         * preceding check will handle this case.
1374
         */
1375
0
        if (papoGroup[iRec - 1]->GetType() != NRT_GEOMETRY)
1376
0
            return TRUE;
1377
0
        else
1378
0
            return FALSE;
1379
0
    }
1380
1381
    /* -------------------------------------------------------------------- */
1382
    /*      Is this a "feature" defining record?  If so break out if it     */
1383
    /*      isn't the first record in the group.                            */
1384
    /* -------------------------------------------------------------------- */
1385
0
    if (papoGroup[0] != nullptr && (poCandidate->GetType() == NRT_NAMEREC ||
1386
0
                                    poCandidate->GetType() == NRT_NODEREC ||
1387
0
                                    poCandidate->GetType() == NRT_LINEREC ||
1388
0
                                    poCandidate->GetType() == NRT_POINTREC ||
1389
0
                                    poCandidate->GetType() == NRT_POLYGON ||
1390
0
                                    poCandidate->GetType() == NRT_CPOLY ||
1391
0
                                    poCandidate->GetType() == NRT_COLLECT ||
1392
0
                                    poCandidate->GetType() == NRT_TEXTREC ||
1393
0
                                    poCandidate->GetType() == NRT_COMMENT))
1394
0
    {
1395
0
        return FALSE;
1396
0
    }
1397
1398
    /* -------------------------------------------------------------------- */
1399
    /*      Do we already have a record of this type?  If so, it likely     */
1400
    /*      doesn't belong in the group.  Attribute records do repeat in    */
1401
    /*      some products.                                                  */
1402
    /* -------------------------------------------------------------------- */
1403
0
    if (poCandidate->GetType() != NRT_ATTREC)
1404
0
    {
1405
0
        int iRec = 0;  // Used after for.
1406
0
        for (; papoGroup[iRec] != nullptr; iRec++)
1407
0
        {
1408
0
            if (poCandidate->GetType() == papoGroup[iRec]->GetType())
1409
0
                break;
1410
0
        }
1411
1412
0
        if (papoGroup[iRec] != nullptr)
1413
0
            return FALSE;
1414
0
    }
1415
1416
0
    return TRUE;
1417
0
}
1418
1419
/************************************************************************/
1420
/*                          ReadRecordGroup()                           */
1421
/*                                                                      */
1422
/*      Read a group of records that form a single feature.             */
1423
/************************************************************************/
1424
1425
NTFRecord **NTFFileReader::ReadRecordGroup()
1426
1427
0
{
1428
1429
0
    ClearCGroup();
1430
1431
    /* -------------------------------------------------------------------- */
1432
    /*      Loop, reading records till we think we have a grouping.         */
1433
    /* -------------------------------------------------------------------- */
1434
0
    int nRecordCount = 0;
1435
0
    NTFRecord *poRecord = nullptr;
1436
0
    while ((poRecord = ReadRecord()) != nullptr &&
1437
0
           poRecord->GetType() != NRT_VTR)
1438
0
    {
1439
0
        if (nRecordCount >= MAX_REC_GROUP)
1440
0
        {
1441
0
            CPLError(CE_Failure, CPLE_AppDefined,
1442
0
                     "Maximum record group size (%d) exceeded.\n",
1443
0
                     MAX_REC_GROUP);
1444
0
            break;
1445
0
        }
1446
1447
0
        if (!pfnRecordGrouper(this, apoCGroup, poRecord))
1448
0
            break;
1449
1450
0
        apoCGroup[nRecordCount++] = poRecord;
1451
0
        apoCGroup[nRecordCount] = nullptr;
1452
0
    }
1453
1454
    /* -------------------------------------------------------------------- */
1455
    /*      Push the last record back on the input queue.                   */
1456
    /* -------------------------------------------------------------------- */
1457
0
    if (poRecord != nullptr)
1458
0
        SaveRecord(poRecord);
1459
1460
    /* -------------------------------------------------------------------- */
1461
    /*      Return the list, or NULL if we didn't get any records.          */
1462
    /* -------------------------------------------------------------------- */
1463
0
    if (nRecordCount == 0)
1464
0
        return nullptr;
1465
0
    else
1466
0
        return apoCGroup;
1467
0
}
1468
1469
/************************************************************************/
1470
/*                          GetFeatureClass()                           */
1471
/************************************************************************/
1472
1473
int NTFFileReader::GetFeatureClass(int iFCIndex, char **ppszFCId,
1474
                                   char **ppszFCName)
1475
1476
0
{
1477
0
    if (iFCIndex < 0 || iFCIndex >= nFCCount)
1478
0
    {
1479
0
        *ppszFCId = nullptr;
1480
0
        *ppszFCName = nullptr;
1481
0
        return FALSE;
1482
0
    }
1483
0
    else
1484
0
    {
1485
0
        *ppszFCId = papszFCNum[iFCIndex];
1486
0
        *ppszFCName = papszFCName[iFCIndex];
1487
0
        return TRUE;
1488
0
    }
1489
0
}
1490
1491
/************************************************************************/
1492
/*                           ReadOGRFeature()                           */
1493
/************************************************************************/
1494
1495
OGRFeature *NTFFileReader::ReadOGRFeature(OGRNTFLayer *poTargetLayer)
1496
1497
0
{
1498
    /* -------------------------------------------------------------------- */
1499
    /*      If this is a raster file, use a custom method to read the       */
1500
    /*      feature.                                                        */
1501
    /* -------------------------------------------------------------------- */
1502
0
    if (IsRasterProduct())
1503
0
        return poRasterLayer->GetNextFeature();
1504
1505
    /* -------------------------------------------------------------------- */
1506
    /*      Loop looking for a group we can translate, and that if          */
1507
    /*      needed matches our layer request.                               */
1508
    /* -------------------------------------------------------------------- */
1509
0
    OGRNTFLayer *poLayer = nullptr;
1510
0
    OGRFeature *poFeature = nullptr;
1511
1512
0
    while (true)
1513
0
    {
1514
0
        NTFRecord **papoGroup = nullptr;
1515
1516
0
        if (GetProductId() == NPC_UNKNOWN && nNTFLevel > 2)
1517
0
            papoGroup = GetNextIndexedRecordGroup(apoCGroup + 1);
1518
0
        else
1519
0
            papoGroup = ReadRecordGroup();
1520
1521
0
        if (papoGroup == nullptr || papoGroup[0] == nullptr)
1522
0
            break;
1523
1524
0
        int nType = papoGroup[0]->GetType();
1525
0
        if (nType < 0 || nType >= (int)(sizeof(apoTypeTranslation) /
1526
0
                                        sizeof(apoTypeTranslation[0])))
1527
0
            continue;
1528
0
        poLayer = apoTypeTranslation[nType];
1529
0
        if (poLayer == nullptr)
1530
0
            continue;
1531
1532
0
        if (poTargetLayer != nullptr && poTargetLayer != poLayer)
1533
0
        {
1534
0
            CacheLineGeometryInGroup(papoGroup);
1535
0
            nSavedFeatureId++;
1536
0
            continue;
1537
0
        }
1538
1539
0
        poFeature = poLayer->FeatureTranslate(this, papoGroup);
1540
0
        if (poFeature == nullptr)
1541
0
        {
1542
            // should this be a real error?
1543
0
            CPLDebug("NTF",
1544
0
                     "FeatureTranslate() failed for a type %d record group\n"
1545
0
                     "in a %s type file.\n",
1546
0
                     papoGroup[0]->GetType(), GetProduct());
1547
0
        }
1548
0
        else
1549
0
        {
1550
0
            break;
1551
0
        }
1552
0
    }
1553
1554
    /* -------------------------------------------------------------------- */
1555
    /*      If we got a feature, set the TILE_REF on it.                    */
1556
    /* -------------------------------------------------------------------- */
1557
0
    if (poFeature != nullptr)
1558
0
    {
1559
0
        int iTileRefField = poLayer->GetLayerDefn()->GetFieldCount() - 1;
1560
1561
0
        CPLAssert(EQUAL(
1562
0
            poLayer->GetLayerDefn()->GetFieldDefn(iTileRefField)->GetNameRef(),
1563
0
            "TILE_REF"));
1564
1565
0
        poFeature->SetField(iTileRefField, GetTileName());
1566
0
        poFeature->SetFID(nSavedFeatureId);
1567
1568
0
        nSavedFeatureId++;
1569
0
    }
1570
1571
    /* -------------------------------------------------------------------- */
1572
    /*      If we got to the end we can establish our feature count for     */
1573
    /*      the file.                                                       */
1574
    /* -------------------------------------------------------------------- */
1575
0
    else
1576
0
    {
1577
        // This assertion was triggered by
1578
        // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=1982 it doesn't
1579
        // look critical enough to be enabled.
1580
        // CPLAssert( nFeatureCount == -1
1581
        //           || nFeatureCount == nSavedFeatureId - nBaseFeatureId );
1582
0
        nFeatureCount = nSavedFeatureId - nBaseFeatureId;
1583
0
    }
1584
1585
0
    return poFeature;
1586
0
}
1587
1588
/************************************************************************/
1589
/*                            TestForLayer()                            */
1590
/*                                                                      */
1591
/*      Return indicator of whether this file contains any features     */
1592
/*      of the indicated layer type.                                    */
1593
/************************************************************************/
1594
1595
int NTFFileReader::TestForLayer(OGRNTFLayer *poLayer)
1596
1597
0
{
1598
0
    for (int i = 0; i < 100; i++)
1599
0
    {
1600
0
        if (apoTypeTranslation[i] == poLayer)
1601
0
            return TRUE;
1602
0
    }
1603
1604
0
    return FALSE;
1605
0
}
1606
1607
/************************************************************************/
1608
/*                            FreshenIndex()                            */
1609
/*                                                                      */
1610
/*      Rebuild the index if it is needed, and currently missing.       */
1611
/************************************************************************/
1612
1613
void NTFFileReader::FreshenIndex()
1614
1615
0
{
1616
0
    if (!bIndexBuilt && bIndexNeeded)
1617
0
        IndexFile();
1618
0
}
1619
1620
/************************************************************************/
1621
/*                             IndexFile()                              */
1622
/*                                                                      */
1623
/*      Read all records beyond the section header and build an         */
1624
/*      internal index of them.                                         */
1625
/************************************************************************/
1626
1627
void NTFFileReader::IndexFile()
1628
1629
0
{
1630
0
    Reset();
1631
1632
0
    DestroyIndex();
1633
1634
0
    bIndexNeeded = TRUE;
1635
0
    bIndexBuilt = TRUE;
1636
0
    bCacheLines = FALSE;
1637
1638
    /* -------------------------------------------------------------------- */
1639
    /*      Process all records after the section header, and before 99     */
1640
    /*      to put them in the index.                                       */
1641
    /* -------------------------------------------------------------------- */
1642
0
    NTFRecord *poRecord = nullptr;
1643
0
    while ((poRecord = ReadRecord()) != nullptr && poRecord->GetType() != 99)
1644
0
    {
1645
0
        const int iType = poRecord->GetType();
1646
0
        const int iId = atoi(poRecord->GetField(3, 8));
1647
1648
0
        if (iType < 0 || iType >= 100)
1649
0
        {
1650
0
            CPLError(CE_Failure, CPLE_AppDefined,
1651
0
                     "Illegal type %d record, skipping.", iType);
1652
0
            delete poRecord;
1653
0
            continue;
1654
0
        }
1655
0
        if (iId < 0)
1656
0
        {
1657
0
            CPLError(CE_Failure, CPLE_AppDefined,
1658
0
                     "Illegal id %d record, skipping.", iId);
1659
0
            delete poRecord;
1660
0
            continue;
1661
0
        }
1662
1663
        /* --------------------------------------------------------------------
1664
         */
1665
        /*      Grow type specific subindex if needed. */
1666
        /* --------------------------------------------------------------------
1667
         */
1668
0
        if (anIndexSize[iType] <= iId)
1669
0
        {
1670
0
            const int nNewSize = std::max(iId + 1, anIndexSize[iType] * 2 + 10);
1671
1672
0
            apapoRecordIndex[iType] = static_cast<NTFRecord **>(
1673
0
                CPLRealloc(apapoRecordIndex[iType], sizeof(void *) * nNewSize));
1674
1675
0
            for (int i = anIndexSize[iType]; i < nNewSize; i++)
1676
0
                (apapoRecordIndex[iType])[i] = nullptr;
1677
1678
0
            anIndexSize[iType] = nNewSize;
1679
0
        }
1680
1681
        /* --------------------------------------------------------------------
1682
         */
1683
        /*      Put record into type specific subindex based on its id as */
1684
        /*      the key. */
1685
        /* --------------------------------------------------------------------
1686
         */
1687
0
        if (apapoRecordIndex[iType][iId] != nullptr)
1688
0
        {
1689
0
            CPLDebug("OGR_NTF",
1690
0
                     "Duplicate record with index %d and type %d\n"
1691
0
                     "in NTFFileReader::IndexFile().",
1692
0
                     iId, iType);
1693
0
            delete apapoRecordIndex[iType][iId];
1694
0
        }
1695
0
        (apapoRecordIndex[iType])[iId] = poRecord;
1696
0
    }
1697
1698
0
    if (poRecord != nullptr)
1699
0
        delete poRecord;
1700
0
}
1701
1702
/************************************************************************/
1703
/*                            DestroyIndex()                            */
1704
/************************************************************************/
1705
1706
void NTFFileReader::DestroyIndex()
1707
1708
2
{
1709
202
    for (int i = 0; i < 100; i++)
1710
200
    {
1711
200
        for (int iId = 0; iId < anIndexSize[i]; iId++)
1712
0
        {
1713
0
            if ((apapoRecordIndex[i])[iId] != nullptr)
1714
0
                delete (apapoRecordIndex[i])[iId];
1715
0
        }
1716
1717
200
        CPLFree(apapoRecordIndex[i]);
1718
200
        apapoRecordIndex[i] = nullptr;
1719
200
        anIndexSize[i] = 0;
1720
200
    }
1721
1722
2
    bIndexBuilt = FALSE;
1723
2
}
1724
1725
/************************************************************************/
1726
/*                          GetIndexedRecord()                          */
1727
/************************************************************************/
1728
1729
NTFRecord *NTFFileReader::GetIndexedRecord(int iType, int iId)
1730
1731
0
{
1732
0
    if ((iType < 0 || iType > 99) || (iId < 0 || iId >= anIndexSize[iType]) ||
1733
0
        (apapoRecordIndex[iType])[iId] == nullptr)
1734
0
    {
1735
        /* If NRT_GEOMETRY3D is an acceptable alternative to 2D */
1736
0
        if (iType == NRT_GEOMETRY)
1737
0
            return GetIndexedRecord(NRT_GEOMETRY3D, iId);
1738
0
        else
1739
0
            return nullptr;
1740
0
    }
1741
1742
0
    return (apapoRecordIndex[iType])[iId];
1743
0
}
1744
1745
/************************************************************************/
1746
/*                          AddToIndexGroup()                           */
1747
/************************************************************************/
1748
1749
void NTFFileReader::AddToIndexGroup(NTFRecord *poRecord)
1750
1751
0
{
1752
0
    int i = 1;  // Used after for.
1753
0
    for (; apoCGroup[i] != nullptr; i++)
1754
0
    {
1755
0
        if (apoCGroup[i] == poRecord)
1756
0
        {
1757
0
            CPLError(CE_Failure, CPLE_AppDefined,
1758
0
                     "Record already inserted in group");
1759
0
            return;
1760
0
        }
1761
0
    }
1762
0
    if (i == MAX_REC_GROUP)
1763
0
    {
1764
0
        CPLError(CE_Failure, CPLE_AppDefined,
1765
0
                 "Maximum number of records in group reached");
1766
0
        delete poRecord;
1767
0
        return;
1768
0
    }
1769
1770
0
    apoCGroup[i] = poRecord;
1771
0
    apoCGroup[i + 1] = nullptr;
1772
0
}
1773
1774
/************************************************************************/
1775
/*                     GetNextIndexedRecordGroup()                      */
1776
/************************************************************************/
1777
1778
NTFRecord **NTFFileReader::GetNextIndexedRecordGroup(NTFRecord **papoPrevGroup)
1779
1780
0
{
1781
0
    int nPrevType, nPrevId;
1782
1783
    /* -------------------------------------------------------------------- */
1784
    /*      What was the identify of our previous anchor record?            */
1785
    /* -------------------------------------------------------------------- */
1786
0
    if (papoPrevGroup == nullptr || papoPrevGroup[0] == nullptr)
1787
0
    {
1788
0
        nPrevType = NRT_POINTREC;
1789
0
        nPrevId = 0;
1790
0
        FreshenIndex();
1791
0
    }
1792
0
    else
1793
0
    {
1794
0
        nPrevType = papoPrevGroup[0]->GetType();
1795
0
        nPrevId = atoi(papoPrevGroup[0]->GetField(3, 8));
1796
0
        if (nPrevId < 0)
1797
0
            return nullptr;
1798
0
    }
1799
1800
    /* -------------------------------------------------------------------- */
1801
    /*      Find the next anchor record.                                    */
1802
    /* -------------------------------------------------------------------- */
1803
0
    NTFRecord *poAnchor = nullptr;
1804
1805
0
    while (nPrevType != 99 && poAnchor == nullptr)
1806
0
    {
1807
0
        nPrevId++;
1808
0
        if (nPrevId >= anIndexSize[nPrevType])
1809
0
        {
1810
0
            do
1811
0
            {
1812
0
                nPrevType++;
1813
0
            } while (nPrevType != NRT_VTR && nPrevType != NRT_NODEREC &&
1814
0
                     nPrevType != NRT_TEXTREC && nPrevType != NRT_NAMEREC &&
1815
0
                     nPrevType != NRT_COLLECT && nPrevType != NRT_POLYGON &&
1816
0
                     nPrevType != NRT_CPOLY && nPrevType != NRT_POINTREC &&
1817
0
                     nPrevType != NRT_LINEREC);
1818
1819
0
            nPrevId = 0;
1820
0
        }
1821
0
        else
1822
0
        {
1823
0
            poAnchor = (apapoRecordIndex[nPrevType])[nPrevId];
1824
0
        }
1825
0
    }
1826
1827
0
    if (poAnchor == nullptr)
1828
0
    {
1829
0
        return nullptr;
1830
0
    }
1831
1832
    /* -------------------------------------------------------------------- */
1833
    /*      Build record group depending on type of anchor and what it      */
1834
    /*      refers to.                                                      */
1835
    /* -------------------------------------------------------------------- */
1836
0
    apoCGroup[0] = nullptr;
1837
0
    apoCGroup[1] = poAnchor;
1838
0
    apoCGroup[2] = nullptr;
1839
1840
    /* -------------------------------------------------------------------- */
1841
    /*      Handle POINTREC/LINEREC                                         */
1842
    /* -------------------------------------------------------------------- */
1843
0
    if (poAnchor->GetType() == NRT_POINTREC ||
1844
0
        poAnchor->GetType() == NRT_LINEREC)
1845
0
    {
1846
0
        int l_nAttCount = 0;
1847
1848
0
        AddToIndexGroup(
1849
0
            GetIndexedRecord(NRT_GEOMETRY, atoi(poAnchor->GetField(9, 14))));
1850
1851
0
        if (poAnchor->GetLength() >= 16)
1852
0
            l_nAttCount = atoi(poAnchor->GetField(15, 16));
1853
1854
0
        for (int iAtt = 0; iAtt < l_nAttCount; iAtt++)
1855
0
        {
1856
0
            AddToIndexGroup(GetIndexedRecord(
1857
0
                NRT_ATTREC,
1858
0
                atoi(poAnchor->GetField(17 + 6 * iAtt, 22 + 6 * iAtt))));
1859
0
        }
1860
0
    }
1861
1862
    /* -------------------------------------------------------------------- */
1863
    /*      Handle TEXTREC                                                  */
1864
    /* -------------------------------------------------------------------- */
1865
0
    else if (poAnchor->GetType() == NRT_TEXTREC)
1866
0
    {
1867
0
        int l_nAttCount = 0;
1868
0
        int nSelCount = 0;
1869
1870
        // Add all the text position records.
1871
0
        nSelCount = atoi(poAnchor->GetField(9, 10));
1872
0
        if (nSelCount < 0)
1873
0
            return nullptr;
1874
1875
0
        for (int iSel = 0; iSel < nSelCount; iSel++)
1876
0
        {
1877
0
            int iStart = 11 + 12 * iSel + 6;
1878
1879
0
            AddToIndexGroup(GetIndexedRecord(
1880
0
                NRT_TEXTPOS, atoi(poAnchor->GetField(iStart, iStart + 5))));
1881
0
        }
1882
1883
        // Add all geometry and TEXR records pointed to by text position
1884
        // records.
1885
0
        for (int iRec = 1; apoCGroup[iRec] != nullptr; iRec++)
1886
0
        {
1887
0
            NTFRecord *poRecord = apoCGroup[iRec];
1888
1889
0
            if (poRecord->GetType() != NRT_TEXTPOS)
1890
0
                continue;
1891
1892
0
            const int nNumTEXR = atoi(poRecord->GetField(9, 10));
1893
0
            for (int iTEXR = 0; iTEXR < nNumTEXR; iTEXR++)
1894
0
            {
1895
0
                AddToIndexGroup(GetIndexedRecord(
1896
0
                    NRT_TEXTREP, atoi(poRecord->GetField(11 + iTEXR * 12,
1897
0
                                                         16 + iTEXR * 12))));
1898
0
                AddToIndexGroup(GetIndexedRecord(
1899
0
                    NRT_GEOMETRY, atoi(poRecord->GetField(17 + iTEXR * 12,
1900
0
                                                          22 + iTEXR * 12))));
1901
0
            }
1902
0
        }
1903
1904
        // Add all the attribute records.
1905
0
        if (poAnchor->GetLength() >= 10 + nSelCount * 12 + 2)
1906
0
            l_nAttCount = atoi(
1907
0
                poAnchor->GetField(11 + nSelCount * 12, 12 + nSelCount * 12));
1908
1909
0
        for (int iAtt = 0; iAtt < l_nAttCount; iAtt++)
1910
0
        {
1911
0
            int iStart = 13 + nSelCount * 12 + 6 * iAtt;
1912
1913
0
            AddToIndexGroup(GetIndexedRecord(
1914
0
                NRT_ATTREC, atoi(poAnchor->GetField(iStart, iStart + 5))));
1915
0
        }
1916
0
    }
1917
1918
    /* -------------------------------------------------------------------- */
1919
    /*      Handle NODEREC.                                                 */
1920
    /* -------------------------------------------------------------------- */
1921
0
    else if (poAnchor->GetType() == NRT_NODEREC)
1922
0
    {
1923
0
        AddToIndexGroup(
1924
0
            GetIndexedRecord(NRT_GEOMETRY, atoi(poAnchor->GetField(9, 14))));
1925
0
    }
1926
1927
    /* -------------------------------------------------------------------- */
1928
    /*      Handle COLLECT.                                                 */
1929
    /* -------------------------------------------------------------------- */
1930
0
    else if (poAnchor->GetType() == NRT_COLLECT)
1931
0
    {
1932
0
        const int nParts = atoi(poAnchor->GetField(9, 12));
1933
0
        if (nParts < 0)
1934
0
            return nullptr;
1935
0
        const int nAttOffset = 13 + nParts * 8;
1936
0
        int l_nAttCount = 0;
1937
1938
0
        if (poAnchor->GetLength() > nAttOffset + 2)
1939
0
            l_nAttCount = atoi(poAnchor->GetField(nAttOffset, nAttOffset + 1));
1940
1941
0
        for (int iAtt = 0; iAtt < l_nAttCount; iAtt++)
1942
0
        {
1943
0
            const int iStart = nAttOffset + 2 + iAtt * 6;
1944
1945
0
            AddToIndexGroup(GetIndexedRecord(
1946
0
                NRT_ATTREC, atoi(poAnchor->GetField(iStart, iStart + 5))));
1947
0
        }
1948
0
    }
1949
1950
    /* -------------------------------------------------------------------- */
1951
    /*      Handle POLYGON                                                  */
1952
    /* -------------------------------------------------------------------- */
1953
0
    else if (poAnchor->GetType() == NRT_POLYGON)
1954
0
    {
1955
0
        AddToIndexGroup(
1956
0
            GetIndexedRecord(NRT_CHAIN, atoi(poAnchor->GetField(9, 14))));
1957
1958
0
        if (poAnchor->GetLength() >= 20)
1959
0
            AddToIndexGroup(GetIndexedRecord(NRT_GEOMETRY,
1960
0
                                             atoi(poAnchor->GetField(15, 20))));
1961
1962
        // Attributes
1963
0
        int l_nAttCount = 0;
1964
1965
0
        if (poAnchor->GetLength() >= 22)
1966
0
            l_nAttCount = atoi(poAnchor->GetField(21, 22));
1967
1968
0
        for (int iAtt = 0; iAtt < l_nAttCount; iAtt++)
1969
0
        {
1970
0
            AddToIndexGroup(GetIndexedRecord(
1971
0
                NRT_ATTREC,
1972
0
                atoi(poAnchor->GetField(23 + 6 * iAtt, 28 + 6 * iAtt))));
1973
0
        }
1974
0
    }
1975
    /* -------------------------------------------------------------------- */
1976
    /*      Handle CPOLY                                                    */
1977
    /* -------------------------------------------------------------------- */
1978
0
    else if (poAnchor->GetType() == NRT_CPOLY)
1979
0
    {
1980
0
        int nPolyCount = atoi(poAnchor->GetField(9, 12));
1981
0
        if (nPolyCount < 0)
1982
0
            return nullptr;
1983
0
        int nPostPoly = nPolyCount * 7 + 12;
1984
1985
0
        if (poAnchor->GetLength() >= nPostPoly + 6)
1986
0
        {
1987
0
            int nGeomId =
1988
0
                atoi(poAnchor->GetField(nPostPoly + 1, nPostPoly + 6));
1989
1990
0
            AddToIndexGroup(GetIndexedRecord(NRT_GEOMETRY, nGeomId));
1991
0
        }
1992
1993
0
        if (poAnchor->GetLength() >= nPostPoly + 8)
1994
0
        {
1995
0
            int l_nAttCount =
1996
0
                atoi(poAnchor->GetField(nPostPoly + 7, nPostPoly + 8));
1997
1998
0
            for (int iAtt = 0; iAtt < l_nAttCount; iAtt++)
1999
0
            {
2000
0
                int nAttId = atoi(poAnchor->GetField(
2001
0
                    nPostPoly + 9 + iAtt * 6, nPostPoly + 14 + iAtt * 6));
2002
0
                AddToIndexGroup(GetIndexedRecord(NRT_ATTREC, nAttId));
2003
0
            }
2004
0
        }
2005
0
    }
2006
2007
0
    return apoCGroup + 1;
2008
0
}
2009
2010
/************************************************************************/
2011
/*                          OverrideTileName()                          */
2012
/************************************************************************/
2013
2014
void NTFFileReader::OverrideTileName(const char *pszNewName)
2015
2016
0
{
2017
0
    CPLFree(pszTileName);
2018
0
    pszTileName = CPLStrdup(pszNewName);
2019
0
}
2020
2021
/************************************************************************/
2022
/*                          CacheAddByGeomId()                          */
2023
/*                                                                      */
2024
/*      Add a geometry to the geometry cache given its GEOMID as        */
2025
/*      the index.                                                      */
2026
/************************************************************************/
2027
2028
void NTFFileReader::CacheAddByGeomId(int nGeomId, OGRGeometry *poGeometry)
2029
2030
0
{
2031
0
    if (!bCacheLines)
2032
0
        return;
2033
2034
0
    CPLAssert(nGeomId >= 0);
2035
2036
    /* -------------------------------------------------------------------- */
2037
    /*      Grow the cache if it isn't large enough to hold the newly       */
2038
    /*      requested geometry id.                                          */
2039
    /* -------------------------------------------------------------------- */
2040
0
    if (nGeomId >= nLineCacheSize)
2041
0
    {
2042
0
        const int nNewSize = nGeomId + 100;
2043
2044
0
        papoLineCache = static_cast<OGRGeometry **>(
2045
0
            CPLRealloc(papoLineCache, sizeof(void *) * nNewSize));
2046
0
        memset(papoLineCache + nLineCacheSize, 0,
2047
0
               sizeof(void *) * (nNewSize - nLineCacheSize));
2048
0
        nLineCacheSize = nNewSize;
2049
0
    }
2050
2051
    /* -------------------------------------------------------------------- */
2052
    /*      Make a cloned copy of the geometry for the cache.               */
2053
    /* -------------------------------------------------------------------- */
2054
0
    if (papoLineCache[nGeomId] != nullptr)
2055
0
        return;
2056
2057
0
    papoLineCache[nGeomId] = poGeometry->clone();
2058
0
}
2059
2060
/************************************************************************/
2061
/*                          CacheGetByGeomId()                          */
2062
/************************************************************************/
2063
2064
OGRGeometry *NTFFileReader::CacheGetByGeomId(int nGeomId)
2065
2066
0
{
2067
0
    if (nGeomId < 0 || nGeomId >= nLineCacheSize)
2068
0
        return nullptr;
2069
0
    else
2070
0
        return papoLineCache[nGeomId];
2071
0
}
2072
2073
/************************************************************************/
2074
/*                             CacheClean()                             */
2075
/************************************************************************/
2076
2077
void NTFFileReader::CacheClean()
2078
2079
6
{
2080
6
    for (int i = 0; i < nLineCacheSize; i++)
2081
0
    {
2082
0
        if (papoLineCache[i] != nullptr)
2083
0
            delete papoLineCache[i];
2084
0
    }
2085
6
    if (papoLineCache != nullptr)
2086
0
        CPLFree(papoLineCache);
2087
2088
6
    nLineCacheSize = 0;
2089
6
    papoLineCache = nullptr;
2090
6
}
2091
2092
/************************************************************************/
2093
/*                      CacheLineGeometryInGroup()                      */
2094
/*                                                                      */
2095
/*      Run any line geometries in this group through the               */
2096
/*      ProcessGeometry() call just to ensure the line geometry will    */
2097
/*      be cached.                                                      */
2098
/************************************************************************/
2099
2100
void NTFFileReader::CacheLineGeometryInGroup(NTFRecord **papoGroup)
2101
2102
0
{
2103
0
    if (!bCacheLines)
2104
0
        return;
2105
2106
0
    for (int iRec = 0; papoGroup[iRec] != nullptr; iRec++)
2107
0
    {
2108
0
        if (papoGroup[iRec]->GetType() == NRT_GEOMETRY ||
2109
0
            papoGroup[iRec]->GetType() == NRT_GEOMETRY3D)
2110
0
        {
2111
0
            OGRGeometry *poGeom = ProcessGeometry(papoGroup[iRec], nullptr);
2112
0
            if (poGeom != nullptr)
2113
0
                delete poGeom;
2114
0
        }
2115
0
    }
2116
0
}
2117
2118
/************************************************************************/
2119
/*                        FormPolygonFromCache()                        */
2120
/*                                                                      */
2121
/*      This method will attempt to find the line geometries            */
2122
/*      referenced by the GEOM_ID_OF_LINK ids of a feature in the       */
2123
/*      line cache (if available), and if so, assemble them into a      */
2124
/*      polygon.                                                        */
2125
/************************************************************************/
2126
2127
int NTFFileReader::FormPolygonFromCache(OGRFeature *poFeature)
2128
2129
0
{
2130
0
    if (!bCacheLines)
2131
0
        return FALSE;
2132
2133
    /* -------------------------------------------------------------------- */
2134
    /*      Collect all the linked lines.                                   */
2135
    /* -------------------------------------------------------------------- */
2136
0
    int nLinkCount = 0;
2137
0
    const int *panLinks =
2138
0
        poFeature->GetFieldAsIntegerList("GEOM_ID_OF_LINK", &nLinkCount);
2139
2140
0
    if (panLinks == nullptr)
2141
0
        return FALSE;
2142
2143
0
    OGRGeometryCollection oLines;
2144
2145
0
    for (int i = 0; i < nLinkCount; i++)
2146
0
    {
2147
0
        OGRGeometry *poLine = CacheGetByGeomId(panLinks[i]);
2148
0
        if (poLine == nullptr)
2149
0
        {
2150
0
            oLines.removeGeometry(-1, FALSE);
2151
0
            return FALSE;
2152
0
        }
2153
2154
0
        oLines.addGeometryDirectly(poLine);
2155
0
    }
2156
2157
    /* -------------------------------------------------------------------- */
2158
    /*      Assemble into a polygon geometry.                               */
2159
    /* -------------------------------------------------------------------- */
2160
0
    OGRGeometry *poGeom = OGRGeometry::FromHandle(OGRBuildPolygonFromEdges(
2161
0
        (OGRGeometryH)&oLines, FALSE, FALSE, 0.1, nullptr));
2162
2163
0
    poFeature->SetGeometryDirectly(poGeom);
2164
2165
0
    oLines.removeGeometry(-1, FALSE);
2166
2167
0
    return poGeom != nullptr;
2168
0
}