• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 16099888070

06 Jul 2025 02:11PM UTC coverage: 71.084% (-0.006%) from 71.09%
16099888070

Pull #12714

github

web-flow
Merge b0b6c5cde into 6c5b62989
Pull Request #12714: PNG: fix caching of other bands that only worked if reading band 1

27 of 34 new or added lines in 1 file covered. (79.41%)

97 existing lines in 32 files now uncovered.

575259 of 809265 relevant lines covered (71.08%)

260080.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.1
/ogr/ogrsf_frmts/mitab/mitab_mapfile.cpp
1
/**********************************************************************
2
 *
3
 * Name:     mitab_mapfile.cpp
4
 * Project:  MapInfo TAB Read/Write library
5
 * Language: C++
6
 * Purpose:  Implementation of the TABMAPFile class used to handle
7
 *           reading/writing of the .MAP files at the MapInfo object level
8
 * Author:   Daniel Morissette, dmorissette@dmsolutions.ca
9
 *
10
 **********************************************************************
11
 * Copyright (c) 1999-2002, Daniel Morissette
12
 * Copyright (c) 2014, Even Rouault <even.rouault at spatialys.com>
13
 *
14
 * SPDX-License-Identifier: MIT
15
 **********************************************************************/
16

17
#include "cpl_port.h"
18
#include "mitab.h"
19

20
#include <cassert>
21
#include <cstddef>
22
#if HAVE_FCNTL_H
23
#include <fcntl.h>
24
#endif
25
#include <algorithm>
26
#include <utility>
27

28
#include "cpl_conv.h"
29
#include "cpl_error.h"
30
#include "cpl_vsi.h"
31
#include "mitab_priv.h"
32
#include "ogr_feature.h"
33

34
/*=====================================================================
35
 *                      class TABMAPFile
36
 *====================================================================*/
37

38
/**********************************************************************
39
 *                   TABMAPFile::TABMAPFile()
40
 *
41
 * Constructor.
42
 **********************************************************************/
43
TABMAPFile::TABMAPFile(const char *pszEncoding)
1,417✔
44
    : m_nMinTABVersion(300), m_pszFname(nullptr), m_fp(nullptr),
45
      m_eAccessMode(TABRead), m_poHeader(nullptr), m_poSpIndex(nullptr),
46
      // See bug 1732: Optimized spatial index produces broken files because of
47
      // the way CoordBlocks are split. For now we have to force using the quick
48
      // (old) spatial index mode by default until bug 1732 is fixed.
49
      m_bQuickSpatialIndexMode(TRUE), m_poIdIndex(nullptr),
50
      m_poCurObjBlock(nullptr), m_nCurObjPtr(-1), m_nCurObjType(TAB_GEOM_UNSET),
51
      m_nCurObjId(-1), m_poCurCoordBlock(nullptr), m_poToolDefTable(nullptr),
52
      m_XMinFilter(0), m_YMinFilter(0), m_XMaxFilter(0), m_YMaxFilter(0),
53
      m_bUpdated(FALSE), m_bLastOpWasRead(FALSE), m_bLastOpWasWrite(FALSE),
54
      m_poSpIndexLeaf(nullptr), m_osEncoding(pszEncoding)
1,417✔
55
{
56
    m_sMinFilter.x = 0;
1,417✔
57
    m_sMinFilter.y = 0;
1,417✔
58
    m_sMaxFilter.x = 0;
1,417✔
59
    m_sMaxFilter.y = 0;
1,417✔
60

61
    m_oBlockManager.SetName("MAP");
1,417✔
62
}
1,417✔
63

64
/**********************************************************************
65
 *                   TABMAPFile::~TABMAPFile()
66
 *
67
 * Destructor.
68
 **********************************************************************/
69
TABMAPFile::~TABMAPFile()
1,417✔
70
{
71
    Close();
1,417✔
72
}
1,417✔
73

74
/**********************************************************************
75
 *                   TABMAPFile::Open()
76
 *
77
 * Compatibility layer with new interface.
78
 * Return 0 on success, -1 in case of failure.
79
 **********************************************************************/
80

81
int TABMAPFile::Open(const char *pszFname, const char *pszAccess,
×
82
                     GBool bNoErrorMsg, int nBlockSizeForCreate)
83
{
84
    // cppcheck-suppress nullPointer
85
    if (STARTS_WITH_CI(pszAccess, "r"))
×
86
        return Open(pszFname, TABRead, bNoErrorMsg, nBlockSizeForCreate);
×
87
    else if (STARTS_WITH_CI(pszAccess, "w"))
×
88
        return Open(pszFname, TABWrite, bNoErrorMsg, nBlockSizeForCreate);
×
89
    else
90
    {
91
        CPLError(CE_Failure, CPLE_FileIO,
×
92
                 "Open() failed: access mode \"%s\" not supported", pszAccess);
93
        return -1;
×
94
    }
95
}
96

97
/**********************************************************************
98
 *                   TABMAPFile::Open()
99
 *
100
 * Open a .MAP file, and initialize the structures to be ready to read
101
 * objects from it.
102
 *
103
 * Since .MAP and .ID files are optional, you can set bNoErrorMsg=TRUE to
104
 * disable the error message and receive an return value of 1 if file
105
 * cannot be opened.
106
 * In this case, only the methods MoveToObjId() and GetCurObjType() can
107
 * be used.  They will behave as if the .ID file contained only null
108
 * references, so all object will look like they have NONE geometries.
109
 *
110
 * Returns 0 on success, 1 when the .map file does not exist, -1 on error.
111
 **********************************************************************/
112
int TABMAPFile::Open(const char *pszFname, TABAccess eAccess,
1,444✔
113
                     GBool bNoErrorMsg /* = FALSE */,
114
                     int nBlockSizeForCreate /* = 512 */)
115
{
116
    CPLErrorReset();
1,444✔
117

118
    VSILFILE *fp = nullptr;
1,444✔
119
    TABRawBinBlock *poBlock = nullptr;
1,444✔
120

121
    if (m_fp)
1,444✔
122
    {
123
        CPLError(CE_Failure, CPLE_FileIO,
×
124
                 "Open() failed: object already contains an open file");
125
        return -1;
×
126
    }
127

128
    m_nMinTABVersion = 300;
1,444✔
129
    m_fp = nullptr;
1,444✔
130
    m_poHeader = nullptr;
1,444✔
131
    m_poIdIndex = nullptr;
1,444✔
132
    m_poSpIndex = nullptr;
1,444✔
133
    m_poToolDefTable = nullptr;
1,444✔
134
    m_eAccessMode = eAccess;
1,444✔
135
    m_bUpdated = FALSE;
1,444✔
136
    m_bLastOpWasRead = FALSE;
1,444✔
137
    m_bLastOpWasWrite = FALSE;
1,444✔
138

139
    if (m_eAccessMode == TABWrite &&
1,444✔
140
        (nBlockSizeForCreate < TAB_MIN_BLOCK_SIZE ||
123✔
141
         nBlockSizeForCreate > TAB_MAX_BLOCK_SIZE ||
122✔
142
         (nBlockSizeForCreate % TAB_MIN_BLOCK_SIZE) != 0))
122✔
143
    {
144
        CPLError(CE_Failure, CPLE_NotSupported,
1✔
145
                 "Open() failed: invalid block size: %d", nBlockSizeForCreate);
146
        return -1;
1✔
147
    }
148

149
    /*-----------------------------------------------------------------
150
     * Open file
151
     *----------------------------------------------------------------*/
152
    const char *pszAccess = (eAccess == TABRead)    ? "rb"
1,443✔
153
                            : (eAccess == TABWrite) ? "wb+"
154
                                                    : "rb+";
155
    fp = VSIFOpenL(pszFname, pszAccess);
1,443✔
156

157
    m_oBlockManager.Reset();
1,443✔
158

159
    if (fp != nullptr &&
1,443✔
160
        (m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite))
1,440✔
161
    {
162
        /*-----------------------------------------------------------------
163
         * Read access: try to read header block
164
         * First try with a 512 bytes block to check the .map version.
165
         * If it is version 500 or more then read again a 1024 bytes block
166
         *----------------------------------------------------------------*/
167
        poBlock = TABCreateMAPBlockFromFile(fp, 0, 512, TRUE, m_eAccessMode);
1,318✔
168

169
        if (poBlock && poBlock->GetBlockClass() == TABMAP_HEADER_BLOCK &&
2,636✔
170
            cpl::down_cast<TABMAPHeaderBlock *>(poBlock)->m_nMAPVersionNumber >=
1,318✔
171
                500)
172
        {
173
            // Version 500 or higher.  Read 1024 bytes block instead of 512
174
            delete poBlock;
1,318✔
175
            poBlock =
176
                TABCreateMAPBlockFromFile(fp, 0, 1024, TRUE, m_eAccessMode);
1,318✔
177
        }
178

179
        if (poBlock == nullptr ||
2,636✔
180
            poBlock->GetBlockClass() != TABMAP_HEADER_BLOCK)
1,318✔
181
        {
182
            if (poBlock)
×
183
                delete poBlock;
×
184
            poBlock = nullptr;
×
185
            VSIFCloseL(fp);
×
186
            CPLError(
×
187
                CE_Failure, CPLE_FileIO,
188
                "Open() failed: %s does not appear to be a valid .MAP file",
189
                pszFname);
190
            return -1;
×
191
        }
192
        m_oBlockManager.SetBlockSize(
2,636✔
193
            cpl::down_cast<TABMAPHeaderBlock *>(poBlock)->m_nRegularBlockSize);
1,318✔
194
    }
195
    else if (fp != nullptr && m_eAccessMode == TABWrite)
125✔
196
    {
197
        /*-----------------------------------------------------------------
198
         * Write access: create a new header block
199
         * .MAP files of Version 500 and up appear to have a 1024 bytes
200
         * header.  The last 512 bytes are usually all zeros.
201
         *----------------------------------------------------------------*/
202
        m_poHeader = new TABMAPHeaderBlock(m_eAccessMode);
122✔
203
        poBlock = m_poHeader;
122✔
204
        poBlock->InitNewBlock(fp, nBlockSizeForCreate, 0);
122✔
205

206
        m_oBlockManager.SetBlockSize(m_poHeader->m_nRegularBlockSize);
122✔
207
        if (m_poHeader->m_nRegularBlockSize == 512)
122✔
208
            m_oBlockManager.SetLastPtr(512);
121✔
209
        else
210
            m_oBlockManager.SetLastPtr(0);
1✔
211

212
        m_bUpdated = TRUE;
122✔
213
    }
214
    else if (bNoErrorMsg)
3✔
215
    {
216
        /*-----------------------------------------------------------------
217
         * .MAP does not exist... produce no error message, but set
218
         * the class members so that MoveToObjId() and GetCurObjType()
219
         * can be used to return only NONE geometries.
220
         *----------------------------------------------------------------*/
221
        m_fp = nullptr;
3✔
222
        m_nCurObjType = TAB_GEOM_NONE;
3✔
223

224
        /* Create a false header block that will return default
225
         * values for projection and coordsys conversion stuff...
226
         */
227
        m_poHeader = new TABMAPHeaderBlock(m_eAccessMode);
3✔
228
        m_poHeader->InitNewBlock(nullptr, 512, 0);
3✔
229

230
        return 1;
3✔
231
    }
232
    else
233
    {
234
        CPLError(CE_Failure, CPLE_FileIO, "Open() failed for %s", pszFname);
×
235
        return -1;
×
236
    }
237

238
    /*-----------------------------------------------------------------
239
     * File appears to be valid... set the various class members
240
     *----------------------------------------------------------------*/
241
    m_fp = fp;
1,440✔
242
    m_poHeader = cpl::down_cast<TABMAPHeaderBlock *>(poBlock);
1,440✔
243
    m_pszFname = CPLStrdup(pszFname);
1,440✔
244

245
    /*-----------------------------------------------------------------
246
     * Create a TABMAPObjectBlock, in READ mode only or in UPDATE mode
247
     * if there's an object
248
     *
249
     * In WRITE mode, the object block will be created only when needed.
250
     * We do not create the object block in the open() call because
251
     * files that contained only "NONE" geometries ended up with empty
252
     * object and spatial index blocks.
253
     *----------------------------------------------------------------*/
254

255
    if (m_eAccessMode == TABRead ||
1,440✔
256
        (m_eAccessMode == TABReadWrite && m_poHeader->m_nFirstIndexBlock != 0))
1,197✔
257
    {
258
        m_poCurObjBlock = new TABMAPObjectBlock(m_eAccessMode);
1,291✔
259
        m_poCurObjBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize);
1,291✔
260
    }
261
    else
262
    {
263
        m_poCurObjBlock = nullptr;
149✔
264
    }
265

266
    /*-----------------------------------------------------------------
267
     * Open associated .ID (object id index) file
268
     *----------------------------------------------------------------*/
269
    m_poIdIndex = new TABIDFile;
1,440✔
270
    if (m_poIdIndex->Open(pszFname, m_eAccessMode) != 0)
1,440✔
271
    {
272
        // Failed... an error has already been reported
273
        Close();
×
274
        return -1;
×
275
    }
276

277
    /*-----------------------------------------------------------------
278
     * Default Coord filter is the MBR of the whole file
279
     * This is currently unused but could eventually be used to handle
280
     * spatial filters more efficiently.
281
     *----------------------------------------------------------------*/
282
    if (m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite)
1,440✔
283
    {
284
        ResetCoordFilter();
1,318✔
285
    }
286

287
    /*-----------------------------------------------------------------
288
     * We could scan a file through its quad tree index... but we don't!
289
     *
290
     * In read mode, we just ignore the spatial index.
291
     *
292
     * In write mode the index is created and maintained as new object
293
     * blocks are added inside CommitObjBlock().
294
     *----------------------------------------------------------------*/
295
    m_poSpIndex = nullptr;
1,440✔
296

297
    if (m_eAccessMode == TABReadWrite)
1,440✔
298
    {
299
        /* We don't allow quick mode in read/write mode */
300
        m_bQuickSpatialIndexMode = FALSE;
1,075✔
301

302
        if (m_poHeader->m_nFirstIndexBlock != 0)
1,075✔
303
        {
304
            poBlock = GetIndexObjectBlock(m_poHeader->m_nFirstIndexBlock);
1,048✔
305
            if (poBlock == nullptr ||
2,096✔
306
                (poBlock->GetBlockType() != TABMAP_INDEX_BLOCK &&
1,048✔
307
                 poBlock->GetBlockType() != TABMAP_OBJECT_BLOCK))
4✔
308
            {
309
                CPLError(CE_Failure, CPLE_AppDefined,
×
310
                         "Cannot find first index block at offset %d",
311
                         m_poHeader->m_nFirstIndexBlock);
×
312
                delete poBlock;
×
313
            }
314
            else if (poBlock->GetBlockType() == TABMAP_INDEX_BLOCK)
1,048✔
315
            {
316
                m_poSpIndex = cpl::down_cast<TABMAPIndexBlock *>(poBlock);
1,044✔
317
                m_poSpIndex->SetMBR(m_poHeader->m_nXMin, m_poHeader->m_nYMin,
1,044✔
318
                                    m_poHeader->m_nXMax, m_poHeader->m_nYMax);
1,044✔
319
            }
320
            else /* if( poBlock->GetBlockType() == TABMAP_OBJECT_BLOCK ) */
321
            {
322
                /* This can happen if the file created by MapInfo contains just
323
                 */
324
                /* a few objects */
325
                delete poBlock;
4✔
326
            }
327
        }
328
    }
329

330
    /*-----------------------------------------------------------------
331
     * Initialization of the Drawing Tools table will be done automatically
332
     * as Read/Write calls are done later.
333
     *----------------------------------------------------------------*/
334
    m_poToolDefTable = nullptr;
1,440✔
335

336
    if (m_eAccessMode == TABReadWrite)
1,440✔
337
    {
338
        InitDrawingTools();
1,075✔
339
    }
340

341
    if (m_eAccessMode == TABReadWrite)
1,440✔
342
    {
343
        VSIStatBufL sStatBuf;
344
        if (VSIStatL(m_pszFname, &sStatBuf) != 0)
1,075✔
345
        {
346
            Close();
×
347
            return -1;
×
348
        }
349
        m_oBlockManager.SetLastPtr(static_cast<int>(
1,075✔
350
            ((sStatBuf.st_size - 1) / m_poHeader->m_nRegularBlockSize) *
1,075✔
351
            m_poHeader->m_nRegularBlockSize));
1,075✔
352

353
        /* Read chain of garbage blocks */
354
        if (m_poHeader->m_nFirstGarbageBlock != 0)
1,075✔
355
        {
356
            int nCurGarbBlock = m_poHeader->m_nFirstGarbageBlock;
×
357
            m_oBlockManager.PushGarbageBlockAsLast(nCurGarbBlock);
×
358
            while (true)
359
            {
360
                GUInt16 nBlockType = 0;
×
361
                int nNextGarbBlockPtr = 0;
×
362
                if (VSIFSeekL(fp, nCurGarbBlock, SEEK_SET) != 0 ||
×
363
                    VSIFReadL(&nBlockType, sizeof(nBlockType), 1, fp) != 1 ||
×
364
                    VSIFReadL(&nNextGarbBlockPtr, sizeof(nNextGarbBlockPtr), 1,
×
365
                              fp) != 1)
366
                {
367
                    CPLError(CE_Failure, CPLE_AppDefined,
×
368
                             "Cannot read garbage block at offset %d",
369
                             nCurGarbBlock);
370
                    break;
×
371
                }
372
                if (nBlockType != TABMAP_GARB_BLOCK)
×
373
                {
374
                    CPLError(CE_Failure, CPLE_AppDefined,
×
375
                             "Got block type (%d) instead of %d at offset %d",
376
                             nBlockType, TABMAP_GARB_BLOCK, nCurGarbBlock);
377
                }
378
                if (nNextGarbBlockPtr == 0)
×
379
                    break;
×
380
                nCurGarbBlock = nNextGarbBlockPtr;
×
381
                m_oBlockManager.PushGarbageBlockAsLast(nCurGarbBlock);
×
382
            }
×
383
        }
384
    }
385

386
    /*-----------------------------------------------------------------
387
     * Make sure all previous calls succeeded.
388
     *----------------------------------------------------------------*/
389
    if (CPLGetLastErrorType() == CE_Failure)
1,440✔
390
    {
391
        // Open Failed... an error has already been reported
392
        Close();
×
393
        return -1;
×
394
    }
395

396
    return 0;
1,440✔
397
}
398

399
/**********************************************************************
400
 *                   TABMAPFile::Close()
401
 *
402
 * Close current file, and release all memory used.
403
 *
404
 * Returns 0 on success, -1 on error.
405
 **********************************************************************/
406
int TABMAPFile::Close()
2,861✔
407
{
408
    // Check if file is opened... it is possible to have a fake header
409
    // without an actual file attached to it.
410
    if (m_fp == nullptr && m_poHeader == nullptr)
2,861✔
411
        return 0;
1,418✔
412

413
    /*----------------------------------------------------------------
414
     * Write access: commit latest changes to the file.
415
     *---------------------------------------------------------------*/
416
    if (m_eAccessMode != TABRead)
1,443✔
417
    {
418
        SyncToDisk();
1,198✔
419
    }
420

421
    // Delete all structures
422
    if (m_poHeader)
1,443✔
423
        delete m_poHeader;
1,443✔
424
    m_poHeader = nullptr;
1,443✔
425

426
    if (m_poIdIndex)
1,443✔
427
    {
428
        m_poIdIndex->Close();
1,440✔
429
        delete m_poIdIndex;
1,440✔
430
        m_poIdIndex = nullptr;
1,440✔
431
    }
432

433
    if (m_poCurObjBlock)
1,443✔
434
    {
435
        delete m_poCurObjBlock;
1,381✔
436
        m_poCurObjBlock = nullptr;
1,381✔
437
        m_nCurObjPtr = -1;
1,381✔
438
        m_nCurObjType = TAB_GEOM_UNSET;
1,381✔
439
        m_nCurObjId = -1;
1,381✔
440
    }
441

442
    if (m_poCurCoordBlock)
1,443✔
443
    {
444
        delete m_poCurCoordBlock;
55✔
445
        m_poCurCoordBlock = nullptr;
55✔
446
    }
447

448
    if (m_poSpIndex)
1,443✔
449
    {
450
        delete m_poSpIndex;
1,142✔
451
        m_poSpIndex = nullptr;
1,142✔
452
        m_poSpIndexLeaf = nullptr;
1,142✔
453
    }
454

455
    if (m_poToolDefTable)
1,443✔
456
    {
457
        delete m_poToolDefTable;
1,325✔
458
        m_poToolDefTable = nullptr;
1,325✔
459
    }
460

461
    // Close file
462
    if (m_fp)
1,443✔
463
        VSIFCloseL(m_fp);
1,440✔
464
    m_fp = nullptr;
1,443✔
465

466
    CPLFree(m_pszFname);
1,443✔
467
    m_pszFname = nullptr;
1,443✔
468

469
    return 0;
1,443✔
470
}
471

472
/************************************************************************/
473
/*                         GetFileSize()                                */
474
/************************************************************************/
475

476
GUInt32 TABMAPFile::GetFileSize()
×
477
{
478
    if (!m_fp)
×
479
        return 0;
×
480
    vsi_l_offset nCurPos = VSIFTellL(m_fp);
×
481
    VSIFSeekL(m_fp, 0, SEEK_END);
×
482
    vsi_l_offset nSize = VSIFTellL(m_fp);
×
483
    VSIFSeekL(m_fp, nCurPos, SEEK_SET);
×
484
    return nSize > UINT_MAX ? UINT_MAX : static_cast<GUInt32>(nSize);
×
485
}
486

487
/************************************************************************/
488
/*                            SyncToDisk()                             */
489
/************************************************************************/
490

491
int TABMAPFile::SyncToDisk()
1,317✔
492
{
493
    if (m_eAccessMode == TABRead)
1,317✔
494
    {
495
        CPLError(CE_Failure, CPLE_NotSupported,
×
496
                 "SyncToDisk() can be used only with Write access.");
497
        return -1;
×
498
    }
499

500
    if (!m_bUpdated)
1,317✔
501
        return 0;
116✔
502

503
    // Start by committing current object and coord blocks
504
    // Nothing happens if none has been created yet.
505
    if (CommitObjAndCoordBlocks(FALSE) != 0)
1,201✔
506
        return -1;
×
507

508
    // Write the drawing tools definitions now.
509
    if (CommitDrawingTools() != 0)
1,201✔
510
        return -1;
×
511

512
    // Commit spatial index blocks
513
    if (CommitSpatialIndex() != 0)
1,201✔
514
        return -1;
×
515

516
    // Update header fields and commit
517
    if (m_poHeader)
1,201✔
518
    {
519
        // OK, with V450 files, objects are not limited to 32k nodes
520
        // any more, and this means that m_nMaxCoordBufSize can become
521
        // huge, and actually more huge than can be held in memory.
522
        // MapInfo counts m_nMaxCoordBufSize=0 for V450 objects, but
523
        // until this is cleanly implemented, we will just prevent
524
        // m_nMaxCoordBufSizefrom going beyond 512k in V450 files.
525
        if (m_nMinTABVersion >= 450)
1,201✔
526
        {
527
            m_poHeader->m_nMaxCoordBufSize =
×
528
                std::min(m_poHeader->m_nMaxCoordBufSize, 512 * 1024);
×
529
        }
530

531
        // Write Ref to beginning of the chain of garbage blocks
532
        m_poHeader->m_nFirstGarbageBlock =
2,402✔
533
            m_oBlockManager.GetFirstGarbageBlock();
1,201✔
534

535
        if (m_poHeader->CommitToFile() != 0)
1,201✔
536
            return -1;
×
537
    }
538

539
    // Check for overflow of internal coordinates and produce a warning
540
    // if that happened...
541
    if (m_poHeader && m_poHeader->m_bIntBoundsOverflow)
1,201✔
542
    {
543
        double dBoundsMinX = 0.0;
×
544
        double dBoundsMinY = 0.0;
×
545
        double dBoundsMaxX = 0.0;
×
546
        double dBoundsMaxY = 0.0;
×
547
        Int2Coordsys(-1000000000, -1000000000, dBoundsMinX, dBoundsMinY);
×
548
        Int2Coordsys(1000000000, 1000000000, dBoundsMaxX, dBoundsMaxY);
×
549

550
        CPLError(CE_Warning,
×
551
                 static_cast<CPLErrorNum>(TAB_WarningBoundsOverflow),
552
                 "Some objects were written outside of the file's "
553
                 "predefined bounds.\n"
554
                 "These objects may have invalid coordinates when the file "
555
                 "is reopened.\n"
556
                 "Predefined bounds: (%.15g,%.15g)-(%.15g,%.15g)\n",
557
                 dBoundsMinX, dBoundsMinY, dBoundsMaxX, dBoundsMaxY);
558
    }
559

560
    if (m_poIdIndex != nullptr && m_poIdIndex->SyncToDisk() != 0)
1,201✔
561
        return -1;
×
562

563
    m_bUpdated = FALSE;
1,201✔
564

565
    return 0;
1,201✔
566
}
567

568
/**********************************************************************
569
 *                   TABMAPFile::ReOpenReadWrite()
570
 **********************************************************************/
571
int TABMAPFile::ReOpenReadWrite()
27✔
572
{
573
    char *pszFname = m_pszFname;
27✔
574
    m_pszFname = nullptr;
27✔
575
    Close();
27✔
576
    if (Open(pszFname, TABReadWrite) < 0)
27✔
577
    {
578
        CPLFree(pszFname);
×
579
        return -1;
×
580
    }
581
    CPLFree(pszFname);
27✔
582
    return 0;
27✔
583
}
584

585
/**********************************************************************
586
 *                   TABMAPFile::SetQuickSpatialIndexMode()
587
 *
588
 * Select "quick spatial index mode".
589
 *
590
 * The default behavior of MITAB is to generate an optimized spatial index,
591
 * but this results in slower write speed.
592
 *
593
 * Applications that want faster write speed and do not care
594
 * about the performance of spatial queries on the resulting file can
595
 * use SetQuickSpatialIndexMode() to require the creation of a non-optimal
596
 * spatial index (actually emulating the type of spatial index produced
597
 * by MITAB before version 1.6.0). In this mode writing files can be
598
 * about 5 times faster, but spatial queries can be up to 30 times slower.
599
 *
600
 * Returns 0 on success, -1 on error.
601
 **********************************************************************/
602
int TABMAPFile::SetQuickSpatialIndexMode(GBool bQuickSpatialIndexMode /*=TRUE*/)
×
603
{
604
    if (m_eAccessMode != TABWrite)
×
605
    {
606
        CPLError(CE_Failure, CPLE_AssertionFailed,
×
607
                 "SetQuickSpatialIndexMode() failed: file not opened for write "
608
                 "access.");
609
        return -1;
×
610
    }
611

612
    if (m_poCurObjBlock != nullptr || m_poSpIndex != nullptr)
×
613
    {
614
        CPLError(CE_Failure, CPLE_AssertionFailed,
×
615
                 "SetQuickSpatialIndexMode() must be called before writing the "
616
                 "first object.");
617
        return -1;
×
618
    }
619

620
    m_bQuickSpatialIndexMode = bQuickSpatialIndexMode;
×
621

622
    return 0;
×
623
}
624

625
/************************************************************************/
626
/*                             PushBlock()                              */
627
/*                                                                      */
628
/*      Install a new block (object or spatial) as being current -      */
629
/*      whatever that means.  This method is only intended to ever      */
630
/*      be called from LoadNextMatchingObjectBlock().                   */
631
/************************************************************************/
632

633
TABRawBinBlock *TABMAPFile::PushBlock(int nFileOffset)
41,407✔
634

635
{
636
    TABRawBinBlock *poBlock = GetIndexObjectBlock(nFileOffset);
41,407✔
637
    if (poBlock == nullptr)
41,407✔
638
        return nullptr;
×
639

640
    if (poBlock->GetBlockType() == TABMAP_INDEX_BLOCK)
41,407✔
641
    {
642
        auto poIndex = std::unique_ptr<TABMAPIndexBlock>(
643
            cpl::down_cast<TABMAPIndexBlock *>(poBlock));
51,624✔
644

645
        if (m_poSpIndexLeaf == nullptr)
25,812✔
646
        {
647
            delete m_poSpIndex;
5✔
648
            m_poSpIndex = poIndex.release();
5✔
649
            m_poSpIndexLeaf = m_poSpIndex;
5✔
650
        }
651
        else
652
        {
653
            CPLAssert(
25,807✔
654
                m_poSpIndexLeaf->GetEntry(m_poSpIndexLeaf->GetCurChildIndex())
655
                    ->nBlockPtr == nFileOffset);
656

657
            m_poSpIndexLeaf->SetCurChild(std::move(poIndex),
25,807✔
658
                                         m_poSpIndexLeaf->GetCurChildIndex());
25,807✔
659
            m_poSpIndexLeaf = m_poSpIndexLeaf->GetCurChild();
25,807✔
660
        }
661
    }
662
    else
663
    {
664
        CPLAssert(poBlock->GetBlockType() == TABMAP_OBJECT_BLOCK);
15,595✔
665

666
        if (m_poCurObjBlock != nullptr)
15,595✔
667
            delete m_poCurObjBlock;
15,595✔
668

669
        m_poCurObjBlock = cpl::down_cast<TABMAPObjectBlock *>(poBlock);
15,595✔
670

671
        m_nCurObjPtr = nFileOffset;
15,595✔
672
        m_nCurObjType = TAB_GEOM_NONE;
15,595✔
673
        m_nCurObjId = -1;
15,595✔
674
    }
675

676
    return poBlock;
41,407✔
677
}
678

679
/************************************************************************/
680
/*                    LoadNextMatchingObjectBlock()                     */
681
/*                                                                      */
682
/*      Advance through the spatial indices till the next object        */
683
/*      block is loaded that matching the spatial query extents.        */
684
/************************************************************************/
685

686
int TABMAPFile::LoadNextMatchingObjectBlock(int bFirstObject)
25,719✔
687

688
{
689
    // If we are just starting, verify the stack is empty.
690
    if (bFirstObject)
25,719✔
691
    {
692
        CPLAssert(m_poSpIndexLeaf == nullptr);
10,624✔
693

694
        /* m_nFirstIndexBlock set to 0 means that there is no feature */
695
        if (m_poHeader->m_nFirstIndexBlock == 0)
10,624✔
696
            return FALSE;
×
697

698
        if (m_poSpIndex != nullptr)
10,624✔
699
        {
700
            m_poSpIndex->UnsetCurChild();
10,619✔
701
            m_poSpIndexLeaf = m_poSpIndex;
10,619✔
702
        }
703
        else
704
        {
705
            if (PushBlock(m_poHeader->m_nFirstIndexBlock) == nullptr)
5✔
706
                return FALSE;
×
707

708
            if (m_poSpIndex == nullptr)
5✔
709
            {
710
                CPLAssert(m_poCurObjBlock != nullptr);
×
711
                return TRUE;
×
712
            }
713
        }
714
    }
715

716
    while (m_poSpIndexLeaf != nullptr)
567,695✔
717
    {
718
        int iEntry = m_poSpIndexLeaf->GetCurChildIndex();
557,571✔
719

720
        if (iEntry >= m_poSpIndexLeaf->GetNumEntries() - 1)
557,571✔
721
        {
722
            TABMAPIndexBlock *poParent = m_poSpIndexLeaf->GetParentRef();
35,931✔
723
            if (m_poSpIndexLeaf == m_poSpIndex)
35,931✔
724
                m_poSpIndex->UnsetCurChild();
10,124✔
725
            m_poSpIndexLeaf = poParent;
35,931✔
726

727
            if (poParent != nullptr)
35,931✔
728
            {
729
                poParent->SetCurChild(nullptr, poParent->GetCurChildIndex());
25,807✔
730
            }
731
            continue;
35,931✔
732
        }
733

734
        m_poSpIndexLeaf->SetCurChild(nullptr, ++iEntry);
521,640✔
735

736
        TABMAPIndexEntry *psEntry = m_poSpIndexLeaf->GetEntry(iEntry);
521,640✔
737
        if (!psEntry)
521,640✔
738
        {
739
            CPLAssert(false);
×
740
            continue;
741
        }
742
        if (psEntry->XMax < m_XMinFilter || psEntry->YMax < m_YMinFilter ||
521,640✔
743
            psEntry->XMin > m_XMaxFilter || psEntry->YMin > m_YMaxFilter)
210,069✔
744
            continue;
480,238✔
745

746
        TABRawBinBlock *poBlock = PushBlock(psEntry->nBlockPtr);
41,402✔
747
        if (poBlock == nullptr)
41,402✔
748
            return FALSE;
×
749
        else if (poBlock->GetBlockType() == TABMAP_OBJECT_BLOCK)
41,402✔
750
            return TRUE;
15,595✔
751
        else
752
        {
753
            /* continue processing new index block */
754
        }
755
    }
756

757
    return false;
10,124✔
758
}
759

760
/************************************************************************/
761
/*                            ResetReading()                            */
762
/*                                                                      */
763
/*      Ensure that any resources related to a spatial traversal of     */
764
/*      the file are recovered, and the state reinitialized to the      */
765
/*      initial conditions.                                             */
766
/************************************************************************/
767

768
void TABMAPFile::ResetReading()
33,821✔
769

770
{
771
    if (m_bLastOpWasWrite)
33,821✔
772
        CommitObjAndCoordBlocks(FALSE);
235✔
773

774
    if (m_poSpIndex)
33,821✔
775
    {
776
        m_poSpIndex->UnsetCurChild();
33,328✔
777
    }
778
    m_poSpIndexLeaf = nullptr;
33,821✔
779

780
    m_bLastOpWasWrite = FALSE;
33,821✔
781
    m_bLastOpWasRead = FALSE;
33,821✔
782
}
33,821✔
783

784
/************************************************************************/
785
/*                          GetNextFeatureId()                          */
786
/*                                                                      */
787
/*      Fetch the next feature id based on a traversal of the           */
788
/*      spatial index.                                                  */
789
/************************************************************************/
790

791
int TABMAPFile::GetNextFeatureId(int nPrevId)
407,107✔
792

793
{
794
    if (m_bLastOpWasWrite)
407,107✔
795
    {
796
        CPLError(CE_Failure, CPLE_AppDefined,
×
797
                 "GetNextFeatureId() cannot be called after write operation");
798
        return -1;
×
799
    }
800
    if (m_eAccessMode == TABWrite)
407,107✔
801
    {
802
        if (ReOpenReadWrite() < 0)
×
803
            return -1;
×
804
    }
805
    m_bLastOpWasRead = TRUE;
407,107✔
806

807
    /* -------------------------------------------------------------------- */
808
    /*      m_fp is NULL when all geometry are NONE and/or there's          */
809
    /*          no .map file and/or there's no spatial indexes              */
810
    /* -------------------------------------------------------------------- */
811
    if (m_fp == nullptr)
407,107✔
812
        return -1;
×
813

814
    if (nPrevId == 0)
407,107✔
815
        nPrevId = -1;
10,624✔
816

817
    /* -------------------------------------------------------------------- */
818
    /*      This should always be true if we are being called properly.     */
819
    /* -------------------------------------------------------------------- */
820
    if (nPrevId != -1 && m_nCurObjId != nPrevId)
407,107✔
821
    {
822
        CPLError(CE_Failure, CPLE_AppDefined,
×
823
                 "TABMAPFile::GetNextFeatureId(%d) called out of sequence.",
824
                 nPrevId);
825
        return -1;
×
826
    }
827

828
    CPLAssert(nPrevId == -1 || m_poCurObjBlock != nullptr);
407,107✔
829

830
    /* -------------------------------------------------------------------- */
831
    /*      Ensure things are initialized properly if this is a request     */
832
    /*      for the first feature.                                          */
833
    /* -------------------------------------------------------------------- */
834
    if (nPrevId == -1)
407,107✔
835
    {
836
        m_nCurObjId = -1;
10,624✔
837
    }
838

839
    /* -------------------------------------------------------------------- */
840
    /*      Try to advance to the next object in the current object         */
841
    /*      block.                                                          */
842
    /* -------------------------------------------------------------------- */
843
    if (nPrevId == -1 || m_poCurObjBlock->AdvanceToNextObject(m_poHeader) == -1)
407,107✔
844
    {
845
        // If not, try to advance to the next object block, and get
846
        // first object from it.  Note that some object blocks actually
847
        // have no objects, so we may have to advance to additional
848
        // object blocks till we find a non-empty one.
849
        GBool bFirstCall = (nPrevId == -1);
25,719✔
UNCOV
850
        do
×
851
        {
852
            if (!LoadNextMatchingObjectBlock(bFirstCall))
25,719✔
853
                return -1;
10,124✔
854

855
            bFirstCall = FALSE;
15,595✔
856
        } while (m_poCurObjBlock->AdvanceToNextObject(m_poHeader) == -1);
15,595✔
857
    }
858

859
    m_nCurObjType = m_poCurObjBlock->GetCurObjectType();
396,983✔
860
    m_nCurObjId = m_poCurObjBlock->GetCurObjectId();
396,983✔
861
    m_nCurObjPtr = m_poCurObjBlock->GetStartAddress() +
396,983✔
862
                   m_poCurObjBlock->GetCurObjectOffset();
396,983✔
863

864
    CPLAssert(m_nCurObjId != -1);
396,983✔
865

866
    return m_nCurObjId;
396,983✔
867
}
868

869
/**********************************************************************
870
 *                   TABMAPFile::Int2Coordsys()
871
 *
872
 * Convert from long integer (internal) to coordinates system units
873
 * as defined in the file's coordsys clause.
874
 *
875
 * Note that the false easting/northing and the conversion factor from
876
 * datum to coordsys units are not included in the calculation.
877
 *
878
 * Returns 0 on success, -1 on error.
879
 **********************************************************************/
880
int TABMAPFile::Int2Coordsys(GInt32 nX, GInt32 nY, double &dX, double &dY)
647,661✔
881
{
882
    if (m_poHeader == nullptr)
647,661✔
883
        return -1;
×
884

885
    return m_poHeader->Int2Coordsys(nX, nY, dX, dY);
647,661✔
886
}
887

888
/**********************************************************************
889
 *                   TABMAPFile::Coordsys2Int()
890
 *
891
 * Convert from coordinates system units as defined in the file's
892
 * coordsys clause to long integer (internal) coordinates.
893
 *
894
 * Note that the false easting/northing and the conversion factor from
895
 * datum to coordsys units are not included in the calculation.
896
 *
897
 * Returns 0 on success, -1 on error.
898
 **********************************************************************/
899
int TABMAPFile::Coordsys2Int(double dX, double dY, GInt32 &nX, GInt32 &nY,
115,245✔
900
                             GBool bIgnoreOverflow /*=FALSE*/)
901
{
902
    if (m_poHeader == nullptr)
115,245✔
903
        return -1;
×
904

905
    return m_poHeader->Coordsys2Int(dX, dY, nX, nY, bIgnoreOverflow);
115,245✔
906
}
907

908
/**********************************************************************
909
 *                   TABMAPFile::Int2CoordsysDist()
910
 *
911
 * Convert a pair of X,Y size (or distance) values from long integer
912
 * (internal) to coordinates system units as defined in the file's coordsys
913
 * clause.
914
 *
915
 * The difference with Int2Coordsys() is that this function only applies
916
 * the scaling factor: it does not apply the displacement.
917
 *
918
 * Since the calculations on the X and Y values are independent, either
919
 * one can be omitted (i.e. passed as 0)
920
 *
921
 * Returns 0 on success, -1 on error.
922
 **********************************************************************/
923
int TABMAPFile::Int2CoordsysDist(GInt32 nX, GInt32 nY, double &dX, double &dY)
12✔
924
{
925
    if (m_poHeader == nullptr)
12✔
926
        return -1;
×
927

928
    return m_poHeader->Int2CoordsysDist(nX, nY, dX, dY);
12✔
929
}
930

931
/**********************************************************************
932
 *                   TABMAPFile::Coordsys2IntDist()
933
 *
934
 * Convert a pair of X,Y size (or distance) values from coordinates
935
 * system units as defined in the file's coordsys clause to long
936
 * integer (internal) coordinate units.
937
 *
938
 * The difference with Int2Coordsys() is that this function only applies
939
 * the scaling factor: it does not apply the displacement.
940
 *
941
 * Since the calculations on the X and Y values are independent, either
942
 * one can be omitted (i.e. passed as 0)
943
 *
944
 * Returns 0 on success, -1 on error.
945
 **********************************************************************/
946
int TABMAPFile::Coordsys2IntDist(double dX, double dY, GInt32 &nX, GInt32 &nY)
4✔
947
{
948
    if (m_poHeader == nullptr)
4✔
949
        return -1;
×
950

951
    return m_poHeader->Coordsys2IntDist(dX, dY, nX, nY);
4✔
952
}
953

954
/**********************************************************************
955
 *                   TABMAPFile::SetCoordsysBounds()
956
 *
957
 * Set projection coordinates bounds of the newly created dataset.
958
 *
959
 * This function must be called after creating a new dataset and before any
960
 * feature can be written to it.
961
 *
962
 * Returns 0 on success, -1 on error.
963
 **********************************************************************/
964
int TABMAPFile::SetCoordsysBounds(double dXMin, double dYMin, double dXMax,
122✔
965
                                  double dYMax)
966
{
967
    if (m_poHeader == nullptr)
122✔
968
        return -1;
×
969

970
    const int nStatus =
971
        m_poHeader->SetCoordsysBounds(dXMin, dYMin, dXMax, dYMax);
122✔
972

973
    if (nStatus == 0)
122✔
974
        ResetCoordFilter();
122✔
975

976
    return nStatus;
122✔
977
}
978

979
/**********************************************************************
980
 *                   TABMAPFile::GetMaxObjId()
981
 *
982
 * Return the value of the biggest valid object id.
983
 *
984
 * Note that object ids are positive and start at 1.
985
 *
986
 * Returns a value >= 0 on success, -1 on error.
987
 **********************************************************************/
988
GInt32 TABMAPFile::GetMaxObjId()
×
989
{
990
    if (m_poIdIndex)
×
991
        return m_poIdIndex->GetMaxObjId();
×
992

993
    return -1;
×
994
}
995

996
/**********************************************************************
997
 *                   TABMAPFile::MoveToObjId()
998
 *
999
 * Get ready to work with the object with the specified id.  The object
1000
 * data pointer (inside m_poCurObjBlock) will be moved to the first byte
1001
 * of data for this map object.
1002
 *
1003
 * The object type and id (i.e. table row number) will be accessible
1004
 * using GetCurObjType() and GetCurObjId().
1005
 *
1006
 * Note that object ids are positive and start at 1.
1007
 *
1008
 * Returns 0 on success, -1 on error.
1009
 **********************************************************************/
1010
int TABMAPFile::MoveToObjId(int nObjId)
701,974✔
1011
{
1012
    if (m_bLastOpWasWrite)
701,974✔
1013
    {
1014
        CPLError(CE_Failure, CPLE_AppDefined,
×
1015
                 "MoveToObjId() cannot be called after write operation");
1016
        return -1;
×
1017
    }
1018
    if (m_eAccessMode == TABWrite)
701,974✔
1019
    {
1020
        if (ReOpenReadWrite() < 0)
27✔
1021
            return -1;
×
1022
    }
1023
    m_bLastOpWasRead = TRUE;
701,974✔
1024

1025
    /*-----------------------------------------------------------------
1026
     * In non creation mode, since the .MAP/.ID are optional, if the
1027
     * file is not opened then we can still act as if one existed and
1028
     * make any object id look like a TAB_GEOM_NONE
1029
     *----------------------------------------------------------------*/
1030
    if (m_fp == nullptr && m_eAccessMode != TABWrite)
701,974✔
1031
    {
1032
        CPLAssert(m_poIdIndex == nullptr && m_poCurObjBlock == nullptr);
22✔
1033
        m_nCurObjPtr = 0;
22✔
1034
        m_nCurObjId = nObjId;
22✔
1035
        m_nCurObjType = TAB_GEOM_NONE;
22✔
1036

1037
        return 0;
22✔
1038
    }
1039

1040
    if (m_poIdIndex == nullptr)
701,952✔
1041
    {
1042
        CPLError(CE_Failure, CPLE_AssertionFailed,
×
1043
                 "MoveToObjId(): file not opened!");
1044
        m_nCurObjPtr = -1;
×
1045
        m_nCurObjId = -1;
×
1046
        m_nCurObjType = TAB_GEOM_UNSET;
×
1047
        return -1;
×
1048
    }
1049

1050
    /*-----------------------------------------------------------------
1051
     * Move map object pointer to the right location.  Fetch location
1052
     * from the index file, unless we are already pointing at it.
1053
     *----------------------------------------------------------------*/
1054
    int nFileOffset =
1055
        m_nCurObjId == nObjId ? m_nCurObjPtr : m_poIdIndex->GetObjPtr(nObjId);
701,952✔
1056

1057
    if (nFileOffset != 0 && m_poCurObjBlock == nullptr)
701,952✔
1058
    {
1059
        CPLError(CE_Failure, CPLE_AssertionFailed,
×
1060
                 "MoveToObjId(): no current object block!");
1061
        m_nCurObjPtr = -1;
×
1062
        m_nCurObjId = -1;
×
1063
        m_nCurObjType = TAB_GEOM_UNSET;
×
1064
        return -1;
×
1065
    }
1066

1067
    if (nFileOffset == 0)
701,952✔
1068
    {
1069
        /*---------------------------------------------------------
1070
         * Object with no geometry... this is a valid case.
1071
         *--------------------------------------------------------*/
1072
        m_nCurObjPtr = 0;
1,287✔
1073
        m_nCurObjId = nObjId;
1,287✔
1074
        m_nCurObjType = TAB_GEOM_NONE;
1,287✔
1075
    }
1076
    else if (m_poCurObjBlock->GotoByteInFile(nFileOffset, TRUE) == 0)
700,665✔
1077
    {
1078
        /*-------------------------------------------------------------
1079
         * OK, it worked, read the object type and row id.
1080
         *------------------------------------------------------------*/
1081
        m_nCurObjPtr = nFileOffset;
700,665✔
1082

1083
        const GByte byVal = m_poCurObjBlock->ReadByte();
700,665✔
1084
        if (IsValidObjType(byVal))
700,665✔
1085
        {
1086
            m_nCurObjType = static_cast<TABGeomType>(byVal);
700,665✔
1087
        }
1088
        else
1089
        {
1090
            CPLError(
×
1091
                CE_Warning,
1092
                static_cast<CPLErrorNum>(TAB_WarningFeatureTypeNotSupported),
1093
                "Unsupported object type %d (0x%2.2x).  Feature will be "
1094
                "returned with NONE geometry.",
1095
                byVal, byVal);
1096
            m_nCurObjType = TAB_GEOM_NONE;
×
1097
        }
1098
        m_nCurObjId = m_poCurObjBlock->ReadInt32();
700,665✔
1099

1100
        // Do a consistency check...
1101
        if (m_nCurObjId != nObjId)
700,665✔
1102
        {
1103
            if (m_nCurObjId == (nObjId | 0x40000000))
×
1104
            {
1105
                CPLError(CE_Failure, CPLE_FileIO,
×
1106
                         "Object %d is marked as deleted in the .MAP file but "
1107
                         "not in the .ID file."
1108
                         "File may be corrupt.",
1109
                         nObjId);
1110
            }
1111
            else
1112
            {
1113
                CPLError(
×
1114
                    CE_Failure, CPLE_FileIO,
1115
                    "Object ID from the .ID file (%d) differs from the value "
1116
                    "in the .MAP file (%d).  File may be corrupt.",
1117
                    nObjId, m_nCurObjId);
1118
            }
1119
            m_nCurObjPtr = -1;
×
1120
            m_nCurObjId = -1;
×
1121
            m_nCurObjType = TAB_GEOM_UNSET;
×
1122
            return -1;
×
1123
        }
1124
    }
1125
    else
1126
    {
1127
        /*---------------------------------------------------------
1128
         * Failed positioning input file... CPLError has been called.
1129
         *--------------------------------------------------------*/
1130
        m_nCurObjPtr = -1;
×
1131
        m_nCurObjId = -1;
×
1132
        m_nCurObjType = TAB_GEOM_UNSET;
×
1133
        return -1;
×
1134
    }
1135

1136
    return 0;
701,952✔
1137
}
1138

1139
/**********************************************************************
1140
 *                   TABMAPFile::MarkAsDeleted()
1141
 *
1142
 * Returns 0 on success, -1 on error.
1143
 **********************************************************************/
1144
int TABMAPFile::MarkAsDeleted()
715✔
1145
{
1146
    if (m_eAccessMode == TABRead)
715✔
1147
        return -1;
×
1148

1149
    if (m_nCurObjPtr <= 0)
715✔
1150
        return 0;
2✔
1151

1152
    int ret = 0;
713✔
1153
    if (m_nCurObjType != TAB_GEOM_NONE)
713✔
1154
    {
1155
        /* Goto offset for object id */
1156
        if (m_poCurObjBlock == nullptr ||
1,426✔
1157
            m_poCurObjBlock->GotoByteInFile(m_nCurObjPtr + 1, TRUE) != 0)
713✔
1158
            return -1;
×
1159

1160
        /* Mark object as deleted */
1161
        m_poCurObjBlock->WriteInt32(m_nCurObjId | 0x40000000);
713✔
1162

1163
        if (m_poCurObjBlock->CommitToFile() != 0)
713✔
1164
            ret = -1;
×
1165
    }
1166

1167
    /* Update index entry to reflect delete state as well */
1168
    if (m_poIdIndex->SetObjPtr(m_nCurObjId, 0) != 0)
713✔
1169
        ret = -1;
×
1170

1171
    m_nCurObjPtr = -1;
713✔
1172
    m_nCurObjId = -1;
713✔
1173
    m_nCurObjType = TAB_GEOM_UNSET;
713✔
1174
    m_bUpdated = TRUE;
713✔
1175

1176
    return ret;
713✔
1177
}
1178

1179
/**********************************************************************
1180
 *                   TABMAPFile::UpdateMapHeaderInfo()
1181
 *
1182
 * Update .map header information (counter of objects by type and minimum
1183
 * required version) in light of a new object to be written to the file.
1184
 *
1185
 * Called only by PrepareNewObj() and by the TABCollection class.
1186
 **********************************************************************/
1187
void TABMAPFile::UpdateMapHeaderInfo(TABGeomType nObjType)
15,021✔
1188
{
1189
    /*-----------------------------------------------------------------
1190
     * Update count of objects by type in the header block
1191
     *----------------------------------------------------------------*/
1192
    if (nObjType == TAB_GEOM_SYMBOL || nObjType == TAB_GEOM_FONTSYMBOL ||
15,021✔
1193
        nObjType == TAB_GEOM_CUSTOMSYMBOL || nObjType == TAB_GEOM_MULTIPOINT ||
330✔
1194
        nObjType == TAB_GEOM_V800_MULTIPOINT || nObjType == TAB_GEOM_SYMBOL_C ||
330✔
1195
        nObjType == TAB_GEOM_FONTSYMBOL_C ||
330✔
1196
        nObjType == TAB_GEOM_CUSTOMSYMBOL_C ||
330✔
1197
        nObjType == TAB_GEOM_MULTIPOINT_C ||
330✔
1198
        nObjType == TAB_GEOM_V800_MULTIPOINT_C)
1199
    {
1200
        m_poHeader->m_numPointObjects++;
14,691✔
1201
    }
1202
    else if (nObjType == TAB_GEOM_LINE || nObjType == TAB_GEOM_PLINE ||
330✔
1203
             nObjType == TAB_GEOM_MULTIPLINE ||
304✔
1204
             nObjType == TAB_GEOM_V450_MULTIPLINE ||
304✔
1205
             nObjType == TAB_GEOM_V800_MULTIPLINE || nObjType == TAB_GEOM_ARC ||
304✔
1206
             nObjType == TAB_GEOM_LINE_C || nObjType == TAB_GEOM_PLINE_C ||
304✔
1207
             nObjType == TAB_GEOM_MULTIPLINE_C ||
92✔
1208
             nObjType == TAB_GEOM_V450_MULTIPLINE_C ||
92✔
1209
             nObjType == TAB_GEOM_V800_MULTIPLINE_C ||
92✔
1210
             nObjType == TAB_GEOM_ARC_C)
1211
    {
1212
        m_poHeader->m_numLineObjects++;
238✔
1213
    }
1214
    else if (nObjType == TAB_GEOM_REGION || nObjType == TAB_GEOM_V450_REGION ||
92✔
1215
             nObjType == TAB_GEOM_V800_REGION || nObjType == TAB_GEOM_RECT ||
86✔
1216
             nObjType == TAB_GEOM_ROUNDRECT || nObjType == TAB_GEOM_ELLIPSE ||
86✔
1217
             nObjType == TAB_GEOM_REGION_C ||
4✔
1218
             nObjType == TAB_GEOM_V450_REGION_C ||
4✔
1219
             nObjType == TAB_GEOM_V800_REGION_C ||
4✔
1220
             nObjType == TAB_GEOM_RECT_C || nObjType == TAB_GEOM_ROUNDRECT_C ||
4✔
1221
             nObjType == TAB_GEOM_ELLIPSE_C)
1222
    {
1223
        m_poHeader->m_numRegionObjects++;
88✔
1224
    }
1225
    else if (nObjType == TAB_GEOM_TEXT || nObjType == TAB_GEOM_TEXT_C)
4✔
1226
    {
1227
        m_poHeader->m_numTextObjects++;
4✔
1228
    }
1229

1230
    /*-----------------------------------------------------------------
1231
     * Check for minimum TAB file version number
1232
     *----------------------------------------------------------------*/
1233
    int nVersion = TAB_GEOM_GET_VERSION(nObjType);
15,021✔
1234

1235
    if (nVersion > m_nMinTABVersion)
15,021✔
1236
    {
1237
        m_nMinTABVersion = nVersion;
×
1238
    }
1239
}
15,021✔
1240

1241
/**********************************************************************
1242
 *                   TABMAPFile::PrepareNewObj()
1243
 *
1244
 * Get ready to write a new object described by poObjHdr (using the
1245
 * poObjHdr's m_nId (featureId), m_nType and IntMBR members which must
1246
 * have been set by the caller).
1247
 *
1248
 * Depending on whether "quick spatial index mode" is selected, we either:
1249
 *
1250
 * 1- Walk through the spatial index to find the best place to insert the
1251
 * new object, update the spatial index references, and prepare the object
1252
 * data block to be ready to write the object to it.
1253
 * ... or ...
1254
 * 2- prepare the current object data block to be ready to write the
1255
 * object to it. If the object block is full then it is inserted in the
1256
 * spatial index and committed to disk, and a new obj block is created.
1257
 *
1258
 * m_poCurObjBlock will be set to be ready to receive the new object, and
1259
 * a new block will be created if necessary (in which case the current
1260
 * block contents will be committed to disk, etc.)  The actual ObjHdr
1261
 * data won't be written to m_poCurObjBlock until CommitNewObj() is called.
1262
 *
1263
 * If this object type uses coordinate blocks, then the coordinate block
1264
 * will be prepared to receive coordinates.
1265
 *
1266
 * This function will also take care of updating the .ID index entry for
1267
 * the new object.
1268
 *
1269
 * Note that object ids are positive and start at 1.
1270
 *
1271
 * Returns 0 on success, -1 on error.
1272
 **********************************************************************/
1273
int TABMAPFile::PrepareNewObj(TABMAPObjHdr *poObjHdr)
15,081✔
1274
{
1275
    m_nCurObjPtr = -1;
15,081✔
1276
    m_nCurObjId = -1;
15,081✔
1277
    m_nCurObjType = TAB_GEOM_UNSET;
15,081✔
1278

1279
    if (m_eAccessMode == TABRead || m_poIdIndex == nullptr ||
15,081✔
1280
        m_poHeader == nullptr)
15,081✔
1281
    {
1282
        CPLError(CE_Failure, CPLE_AssertionFailed,
×
1283
                 "PrepareNewObj() failed: file not opened for write access.");
1284
        return -1;
×
1285
    }
1286

1287
    if (m_bLastOpWasRead)
15,081✔
1288
    {
1289
        m_bLastOpWasRead = FALSE;
114✔
1290
        if (m_poSpIndex)
114✔
1291
        {
1292
            m_poSpIndex->UnsetCurChild();
112✔
1293
        }
1294
    }
1295

1296
    /*-----------------------------------------------------------------
1297
     * For objects with no geometry, we just update the .ID file and return
1298
     *----------------------------------------------------------------*/
1299
    if (poObjHdr->m_nType == TAB_GEOM_NONE)
15,081✔
1300
    {
1301
        m_nCurObjType = poObjHdr->m_nType;
60✔
1302
        m_nCurObjId = poObjHdr->m_nId;
60✔
1303
        m_nCurObjPtr = 0;
60✔
1304
        m_poIdIndex->SetObjPtr(m_nCurObjId, 0);
60✔
1305

1306
        return 0;
60✔
1307
    }
1308

1309
    /*-----------------------------------------------------------------
1310
     * Update count of objects by type in the header block and minimum
1311
     * required version.
1312
     *----------------------------------------------------------------*/
1313
    UpdateMapHeaderInfo(poObjHdr->m_nType);
15,021✔
1314

1315
    /*-----------------------------------------------------------------
1316
     * Depending on the selected spatial index mode, we will either insert
1317
     * new objects via the spatial index (slower write but results in optimal
1318
     * spatial index) or directly in the current ObjBlock (faster write
1319
     * but non-optimal spatial index)
1320
     *----------------------------------------------------------------*/
1321
    if (!m_bQuickSpatialIndexMode)
15,021✔
1322
    {
1323
        if (PrepareNewObjViaSpatialIndex(poObjHdr) != 0)
11,568✔
1324
            return -1; /* Error already reported */
×
1325
    }
1326
    else
1327
    {
1328
        if (PrepareNewObjViaObjBlock(poObjHdr) != 0)
3,453✔
1329
            return -1; /* Error already reported */
×
1330
    }
1331

1332
    /*-----------------------------------------------------------------
1333
     * Prepare ObjBlock for this new object.
1334
     * Real data won't be written to the object block until CommitNewObj()
1335
     * is called.
1336
     *----------------------------------------------------------------*/
1337
    m_nCurObjPtr = m_poCurObjBlock->PrepareNewObject(poObjHdr);
15,021✔
1338
    if (m_nCurObjPtr < 0)
15,021✔
1339
    {
1340
        CPLError(CE_Failure, CPLE_FileIO,
×
1341
                 "Failed writing object header for feature id %d",
1342
                 poObjHdr->m_nId);
1343
        return -1;
×
1344
    }
1345

1346
    m_nCurObjType = poObjHdr->m_nType;
15,021✔
1347
    m_nCurObjId = poObjHdr->m_nId;
15,021✔
1348

1349
    /*-----------------------------------------------------------------
1350
     * Update .ID Index
1351
     *----------------------------------------------------------------*/
1352
    m_poIdIndex->SetObjPtr(m_nCurObjId, m_nCurObjPtr);
15,021✔
1353

1354
    /*-----------------------------------------------------------------
1355
     * Prepare Coords block...
1356
     * create a new TABMAPCoordBlock if it was not done yet.
1357
     *----------------------------------------------------------------*/
1358
    PrepareCoordBlock(m_nCurObjType, m_poCurObjBlock, &m_poCurCoordBlock);
15,021✔
1359

1360
    if (CPLGetLastErrorType() == CE_Failure)
15,021✔
1361
        return -1;
×
1362

1363
    m_bUpdated = TRUE;
15,021✔
1364
    m_bLastOpWasWrite = TRUE;
15,021✔
1365

1366
    return 0;
15,021✔
1367
}
1368

1369
/**********************************************************************
1370
 *                   TABMAPFile::PrepareNewObjViaSpatialIndex()
1371
 *
1372
 * Used by TABMAPFile::PrepareNewObj() to walk through the spatial index
1373
 * to find the best place to insert the new object, update the spatial
1374
 * index references, and prepare the object data block to be ready to
1375
 * write the object to it.
1376
 *
1377
 * This method is used when "quick spatial index mode" is NOT selected,
1378
 * i.e. when we want to produce a file with an optimal spatial index
1379
 *
1380
 * Returns 0 on success, -1 on error.
1381
 **********************************************************************/
1382
int TABMAPFile::PrepareNewObjViaSpatialIndex(TABMAPObjHdr *poObjHdr)
11,568✔
1383
{
1384
    GInt32 nObjBlockForInsert = -1;
11,568✔
1385

1386
    /*-----------------------------------------------------------------
1387
     * Create spatial index if we don't have one yet.
1388
     * We do not create the index and object data blocks in the open()
1389
     * call because files that contained only "NONE" geometries ended up
1390
     * with empty object and spatial index blocks.
1391
     *----------------------------------------------------------------*/
1392
    if (m_poSpIndex == nullptr)
11,568✔
1393
    {
1394
        // Spatial Index not created yet...
1395
        m_poSpIndex = new TABMAPIndexBlock(m_eAccessMode);
6✔
1396

1397
        m_poSpIndex->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
6✔
1398
                                  m_oBlockManager.AllocNewBlock("INDEX"));
1399
        m_poSpIndex->SetMAPBlockManagerRef(&m_oBlockManager);
6✔
1400

1401
        if (m_eAccessMode == TABReadWrite &&
6✔
1402
            m_poHeader->m_nFirstIndexBlock != 0)
6✔
1403
        {
1404
            /* This can happen if the file created by MapInfo contains just */
1405
            /* a few objects */
1406
            TABRawBinBlock *poBlock =
1407
                GetIndexObjectBlock(m_poHeader->m_nFirstIndexBlock);
3✔
1408
            CPLAssert(poBlock != nullptr &&
3✔
1409
                      poBlock->GetBlockType() == TABMAP_OBJECT_BLOCK);
1410
            delete poBlock;
3✔
1411

1412
            if (m_poSpIndex->AddEntry(m_poHeader->m_nXMin, m_poHeader->m_nYMin,
6✔
1413
                                      m_poHeader->m_nXMax, m_poHeader->m_nYMax,
3✔
1414
                                      m_poHeader->m_nFirstIndexBlock) != 0)
3✔
1415
                return -1;
×
1416

1417
            delete m_poCurObjBlock;
3✔
1418
            m_poCurObjBlock = nullptr;
3✔
1419
            delete m_poCurCoordBlock;
3✔
1420
            m_poCurCoordBlock = nullptr;
3✔
1421
        }
1422

1423
        m_poHeader->m_nFirstIndexBlock = m_poSpIndex->GetNodeBlockPtr();
6✔
1424

1425
        /* We'll also need to create an object data block (later) */
1426
        // nObjBlockForInsert = -1;
1427

1428
        CPLAssert(m_poCurObjBlock == nullptr);
6✔
1429
    }
1430
    else
1431
    /*-----------------------------------------------------------------
1432
     * Search the spatial index to find the best place to insert this
1433
     * new object.
1434
     *----------------------------------------------------------------*/
1435
    {
1436
        nObjBlockForInsert = m_poSpIndex->ChooseLeafForInsert(
11,562✔
1437
            poObjHdr->m_nMinX, poObjHdr->m_nMinY, poObjHdr->m_nMaxX,
1438
            poObjHdr->m_nMaxY);
1439
        if (nObjBlockForInsert == -1)
11,562✔
1440
        {
1441
            /* ChooseLeafForInsert() should not fail unless file is corrupt*/
1442
            CPLError(CE_Failure, CPLE_AssertionFailed,
×
1443
                     "ChooseLeafForInsert() Failed?!?!");
1444
            return -1;
×
1445
        }
1446
    }
1447

1448
    if (nObjBlockForInsert == -1)
11,568✔
1449
    {
1450
        /*-------------------------------------------------------------
1451
         * Create a new object data block from scratch
1452
         *------------------------------------------------------------*/
1453
        m_poCurObjBlock = new TABMAPObjectBlock(TABReadWrite);
6✔
1454

1455
        int nBlockOffset = m_oBlockManager.AllocNewBlock("OBJECT");
6✔
1456

1457
        m_poCurObjBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
6✔
1458
                                      nBlockOffset);
1459

1460
        /*-------------------------------------------------------------
1461
         * Insert new object block in index, based on MBR of poObjHdr
1462
         *------------------------------------------------------------*/
1463
        if (m_poSpIndex->AddEntry(poObjHdr->m_nMinX, poObjHdr->m_nMinY,
6✔
1464
                                  poObjHdr->m_nMaxX, poObjHdr->m_nMaxY,
1465
                                  m_poCurObjBlock->GetStartAddress()) != 0)
12✔
1466
            return -1;
×
1467

1468
        m_poCurObjBlock->SetMBR(poObjHdr->m_nMinX, poObjHdr->m_nMinY,
6✔
1469
                                poObjHdr->m_nMaxX, poObjHdr->m_nMaxY);
1470

1471
        const int nNextDepth = m_poSpIndex->GetCurMaxDepth() + 1;
6✔
1472
        m_poHeader->m_nMaxSpIndexDepth = static_cast<GByte>(std::max(
6✔
1473
            static_cast<int>(m_poHeader->m_nMaxSpIndexDepth), nNextDepth));
6✔
1474
    }
1475
    else
1476
    {
1477
        /*-------------------------------------------------------------
1478
         * Load existing object and Coord blocks, unless we've already
1479
         * got the right object block in memory
1480
         *------------------------------------------------------------*/
1481
        if (m_poCurObjBlock &&
23,124✔
1482
            m_poCurObjBlock->GetStartAddress() != nObjBlockForInsert)
11,562✔
1483
        {
1484
            /* Got a block in memory but it is not the right one, flush it */
1485
            if (CommitObjAndCoordBlocks(TRUE) != 0)
10,720✔
1486
                return -1;
×
1487
        }
1488

1489
        if (m_poCurObjBlock == nullptr)
11,562✔
1490
        {
1491
            if (LoadObjAndCoordBlocks(nObjBlockForInsert) != 0)
10,720✔
1492
                return -1;
×
1493
        }
1494

1495
        /* If we have compressed objects, we don't want to change the center  */
1496
        m_poCurObjBlock->LockCenter();
11,562✔
1497

1498
        // Check if the ObjBlock know its MBR. If not (new block, or the current
1499
        // block was the good one but retrieved without the index), get the
1500
        // value from the index and set it.
1501
        GInt32 nMinX, nMinY, nMaxX, nMaxY;
1502
        m_poCurObjBlock->GetMBR(nMinX, nMinY, nMaxX, nMaxY);
11,562✔
1503
        if (nMinX > nMaxX)
11,562✔
1504
        {
1505
            m_poSpIndex->GetCurLeafEntryMBR(m_poCurObjBlock->GetStartAddress(),
10,724✔
1506
                                            nMinX, nMinY, nMaxX, nMaxY);
1507
            m_poCurObjBlock->SetMBR(nMinX, nMinY, nMaxX, nMaxY);
10,724✔
1508
        }
1509
    }
1510

1511
    /*-----------------------------------------------------------------
1512
     * Fetch new object size, make sure there is enough room in obj.
1513
     * block for new object, update spatial index and split if necessary.
1514
     *----------------------------------------------------------------*/
1515
    int nObjSize = m_poHeader->GetMapObjectSize(poObjHdr->m_nType);
11,568✔
1516

1517
    /*-----------------------------------------------------------------
1518
     * But first check if we can recover space from this block in case
1519
     * there are deleted objects in it.
1520
     *----------------------------------------------------------------*/
1521
    if (m_poCurObjBlock->GetNumUnusedBytes() < nObjSize)
11,568✔
1522
    {
1523
        std::vector<std::unique_ptr<TABMAPObjHdr>> apoSrcObjHdrs;
512✔
1524
        int nObjectSpace = 0;
512✔
1525

1526
        /* First pass to enumerate valid objects and compute their accumulated
1527
           required size. */
1528
        m_poCurObjBlock->Rewind();
512✔
1529
        while (auto poExistingObjHdr =
1530
                   TABMAPObjHdr::ReadNextObj(m_poCurObjBlock, m_poHeader))
11,273✔
1531
        {
1532
            nObjectSpace +=
10,761✔
1533
                m_poHeader->GetMapObjectSize(poExistingObjHdr->m_nType);
10,761✔
1534
            apoSrcObjHdrs.emplace_back(poExistingObjHdr);
10,761✔
1535
        }
10,761✔
1536

1537
        /* Check that there's really some place that can be recovered */
1538
        if (nObjectSpace < m_poHeader->m_nRegularBlockSize - 20 -
1,024✔
1539
                               m_poCurObjBlock->GetNumUnusedBytes())
512✔
1540
        {
1541
#ifdef DEBUG_VERBOSE
1542
            CPLDebug("MITAB",
1543
                     "Compacting block at offset %d, %d objects valid, "
1544
                     "recovering %d bytes",
1545
                     m_poCurObjBlock->GetStartAddress(),
1546
                     static_cast<int>(apoSrcObjHdrs.size()),
1547
                     (m_poHeader->m_nRegularBlockSize - 20 -
1548
                      m_poCurObjBlock->GetNumUnusedBytes()) -
1549
                         nObjectSpace);
1550
#endif
1551
            m_poCurObjBlock->ClearObjects();
272✔
1552

1553
            for (auto &poSrcObjHdrs : apoSrcObjHdrs)
2,745✔
1554
            {
1555
                /*-----------------------------------------------------------------
1556
                 * Prepare and Write ObjHdr to this ObjBlock
1557
                 *----------------------------------------------------------------*/
1558
                int nObjPtr =
1559
                    m_poCurObjBlock->PrepareNewObject(poSrcObjHdrs.get());
2,473✔
1560
                if (nObjPtr < 0 ||
4,946✔
1561
                    m_poCurObjBlock->CommitNewObject(poSrcObjHdrs.get()) != 0)
2,473✔
1562
                {
1563
                    CPLError(CE_Failure, CPLE_FileIO,
×
1564
                             "Failed writing object header for feature id %d",
1565
                             poSrcObjHdrs->m_nId);
×
1566
                    return -1;
×
1567
                }
1568

1569
                /*-----------------------------------------------------------------
1570
                 * Update .ID Index
1571
                 *----------------------------------------------------------------*/
1572
                m_poIdIndex->SetObjPtr(poSrcObjHdrs->m_nId, nObjPtr);
2,473✔
1573
            }
1574
        }
1575
    }
1576

1577
    if (m_poCurObjBlock->GetNumUnusedBytes() >= nObjSize)
11,568✔
1578
    {
1579
        /*-------------------------------------------------------------
1580
         * New object fits in current block, just update the spatial index
1581
         *------------------------------------------------------------*/
1582
        GInt32 nMinX, nMinY, nMaxX, nMaxY;
1583
        m_poCurObjBlock->GetMBR(nMinX, nMinY, nMaxX, nMaxY);
11,328✔
1584

1585
        /* Need to calculate the enlarged MBR that includes new object */
1586
        nMinX = std::min(nMinX, poObjHdr->m_nMinX);
11,328✔
1587
        nMinY = std::min(nMinY, poObjHdr->m_nMinY);
11,328✔
1588
        nMaxX = std::max(nMaxX, poObjHdr->m_nMaxX);
11,328✔
1589
        nMaxY = std::max(nMaxY, poObjHdr->m_nMaxY);
11,328✔
1590

1591
        m_poCurObjBlock->SetMBR(nMinX, nMinY, nMaxX, nMaxY);
11,328✔
1592

1593
        if (m_poSpIndex->UpdateLeafEntry(m_poCurObjBlock->GetStartAddress(),
11,328✔
1594
                                         nMinX, nMinY, nMaxX, nMaxY) != 0)
11,328✔
1595
            return -1;
×
1596
    }
1597
    else
1598
    {
1599
        /*-------------------------------------------------------------
1600
         * OK, the new object won't fit in the current block, need to split
1601
         * and update index.
1602
         * Split() does its job so that the current obj block will remain
1603
         * the best candidate to receive the new object. It also flushes
1604
         * everything to disk and will update m_poCurCoordBlock to point to
1605
         * the last coord block in the chain, ready to accept new data
1606
         *------------------------------------------------------------*/
1607
        auto poNewObjBlock = std::unique_ptr<TABMAPObjectBlock>(
1608
            SplitObjBlock(poObjHdr, nObjSize));
240✔
1609

1610
        if (poNewObjBlock == nullptr)
240✔
1611
            return -1; /* Split failed, error already reported. */
×
1612

1613
        /*-------------------------------------------------------------
1614
         * Update index with info about m_poCurObjectBlock *first*
1615
         * This is important since UpdateLeafEntry() needs the chain of
1616
         * index nodes preloaded by ChooseLeafEntry() in order to do its job
1617
         *------------------------------------------------------------*/
1618
        GInt32 nMinX = 0;
240✔
1619
        GInt32 nMinY = 0;
240✔
1620
        GInt32 nMaxX = 0;
240✔
1621
        GInt32 nMaxY = 0;
240✔
1622
        m_poCurObjBlock->GetMBR(nMinX, nMinY, nMaxX, nMaxY);
240✔
1623
        CPLAssert(nMinX <= nMaxX);
240✔
1624

1625
        /* Need to calculate the enlarged MBR that includes new object */
1626
        nMinX = std::min(nMinX, poObjHdr->m_nMinX);
240✔
1627
        nMinY = std::min(nMinY, poObjHdr->m_nMinY);
240✔
1628
        nMaxX = std::max(nMaxX, poObjHdr->m_nMaxX);
240✔
1629
        nMaxY = std::max(nMaxY, poObjHdr->m_nMaxY);
240✔
1630

1631
        m_poCurObjBlock->SetMBR(nMinX, nMinY, nMaxX, nMaxY);
240✔
1632

1633
        if (m_poSpIndex->UpdateLeafEntry(m_poCurObjBlock->GetStartAddress(),
240✔
1634
                                         nMinX, nMinY, nMaxX, nMaxY) != 0)
240✔
1635
            return -1;
×
1636

1637
        /*-------------------------------------------------------------
1638
         * Add new obj block to index
1639
         *------------------------------------------------------------*/
1640
        poNewObjBlock->GetMBR(nMinX, nMinY, nMaxX, nMaxY);
240✔
1641
        CPLAssert(nMinX <= nMaxX);
240✔
1642

1643
        if (m_poSpIndex->AddEntry(nMinX, nMinY, nMaxX, nMaxY,
240✔
1644
                                  poNewObjBlock->GetStartAddress()) != 0)
480✔
1645
            return -1;
×
1646
        const int nNextDepth = m_poSpIndex->GetCurMaxDepth() + 1;
240✔
1647
        m_poHeader->m_nMaxSpIndexDepth = static_cast<GByte>(std::max(
240✔
1648
            static_cast<int>(m_poHeader->m_nMaxSpIndexDepth), nNextDepth));
240✔
1649

1650
        /*-------------------------------------------------------------
1651
         * Implicitly delete second object block, no need to commit to file
1652
         *first since it is already been committed to disk by Split()
1653
         *------------------------------------------------------------*/
1654
    }
1655

1656
    return 0;
11,568✔
1657
}
1658

1659
/**********************************************************************
1660
 *                   TABMAPFile::PrepareNewObjViaObjBlock()
1661
 *
1662
 * Used by TABMAPFile::PrepareNewObj() to prepare the current object
1663
 * data block to be ready to write the object to it. If the object block
1664
 * is full then it is inserted in the spatial index and committed to disk,
1665
 * and a new obj block is created.
1666
 *
1667
 * This method is used when "quick spatial index mode" is selected,
1668
 * i.e. faster write, but non-optimal spatial index.
1669
 *
1670
 * Returns 0 on success, -1 on error.
1671
 **********************************************************************/
1672
int TABMAPFile::PrepareNewObjViaObjBlock(TABMAPObjHdr *poObjHdr)
3,453✔
1673
{
1674
    /*-------------------------------------------------------------
1675
     * We will need an object block... check if it exists and
1676
     * create it if it has not been created yet (first time for this file).
1677
     * We do not create the object block in the open() call because
1678
     * files that contained only "NONE" geometries ended up with empty
1679
     * object and spatial index blocks.
1680
     * Note: A coord block will be created only if needed later.
1681
     *------------------------------------------------------------*/
1682
    if (m_poCurObjBlock == nullptr)
3,453✔
1683
    {
1684
        m_poCurObjBlock = new TABMAPObjectBlock(m_eAccessMode);
87✔
1685

1686
        int nBlockOffset = m_oBlockManager.AllocNewBlock("OBJECT");
87✔
1687

1688
        m_poCurObjBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
87✔
1689
                                      nBlockOffset);
1690

1691
        // The reference to the first object block should
1692
        // actually go through the index blocks... this will be
1693
        // updated when file is closed.
1694
        m_poHeader->m_nFirstIndexBlock = nBlockOffset;
87✔
1695
    }
1696

1697
    /*-----------------------------------------------------------------
1698
     * Fetch new object size, make sure there is enough room in obj.
1699
     * block for new object, and save/create a new one if necessary.
1700
     *----------------------------------------------------------------*/
1701
    const int nObjSize = m_poHeader->GetMapObjectSize(poObjHdr->m_nType);
3,453✔
1702
    if (m_poCurObjBlock->GetNumUnusedBytes() < nObjSize)
3,453✔
1703
    {
1704
        /*-------------------------------------------------------------
1705
         * OK, the new object won't fit in the current block. Add the
1706
         * current block to the spatial index, commit it to disk and init
1707
         * a new block
1708
         *------------------------------------------------------------*/
1709
        CommitObjAndCoordBlocks(FALSE);
90✔
1710

1711
        if (m_poCurObjBlock->InitNewBlock(
90✔
1712
                m_fp, m_poHeader->m_nRegularBlockSize,
90✔
1713
                m_oBlockManager.AllocNewBlock("OBJECT")) != 0)
90✔
1714
            return -1; /* Error already reported */
×
1715

1716
        /*-------------------------------------------------------------
1717
         * Coord block has been committed to disk but not deleted.
1718
         * Delete it to require the creation of a new coord block chain
1719
         * as needed.
1720
         *-------------------------------------------------------------*/
1721
        if (m_poCurCoordBlock)
90✔
1722
        {
1723
            delete m_poCurCoordBlock;
1✔
1724
            m_poCurCoordBlock = nullptr;
1✔
1725
        }
1726
    }
1727

1728
    return 0;
3,453✔
1729
}
1730

1731
/**********************************************************************
1732
 *                   TABMAPFile::CommitNewObj()
1733
 *
1734
 * Commit object header data to the ObjBlock. Should be called after
1735
 * PrepareNewObj, once all members of the ObjHdr have been set.
1736
 *
1737
 * Returns 0 on success, -1 on error.
1738
 **********************************************************************/
1739
int TABMAPFile::CommitNewObj(TABMAPObjHdr *poObjHdr)
15,081✔
1740
{
1741
    // Nothing to do for NONE objects
1742
    if (poObjHdr->m_nType == TAB_GEOM_NONE)
15,081✔
1743
    {
1744
        return 0;
60✔
1745
    }
1746

1747
    /* Update this now so that PrepareCoordBlock() doesn't try to old an older
1748
     */
1749
    /* block */
1750
    if (m_poCurCoordBlock != nullptr)
15,021✔
1751
        m_poCurObjBlock->AddCoordBlockRef(m_poCurCoordBlock->GetStartAddress());
326✔
1752

1753
    /* So that GetExtent() is up-to-date */
1754
    if (m_poSpIndex != nullptr)
15,021✔
1755
    {
1756
        m_poSpIndex->GetMBR(m_poHeader->m_nXMin, m_poHeader->m_nYMin,
14,611✔
1757
                            m_poHeader->m_nXMax, m_poHeader->m_nYMax);
14,611✔
1758
    }
1759

1760
    return m_poCurObjBlock->CommitNewObject(poObjHdr);
15,021✔
1761
}
1762

1763
/**********************************************************************
1764
 *                   TABMAPFile::CommitObjAndCoordBlocks()
1765
 *
1766
 * Commit the TABMAPObjBlock and TABMAPCoordBlock to disk.
1767
 *
1768
 * The objects are deleted from memory if bDeleteObjects==TRUE.
1769
 *
1770
 * Returns 0 on success, -1 on error.
1771
 **********************************************************************/
1772
int TABMAPFile::CommitObjAndCoordBlocks(GBool bDeleteObjects /*=FALSE*/)
12,246✔
1773
{
1774
    int nStatus = 0;
12,246✔
1775

1776
    /*-----------------------------------------------------------------
1777
     * First check that a objBlock has been created.  It is possible to have
1778
     * no object block in files that contain only "NONE" geometries.
1779
     *----------------------------------------------------------------*/
1780
    if (m_poCurObjBlock == nullptr)
12,246✔
1781
        return 0;
36✔
1782

1783
    if (m_eAccessMode == TABRead)
12,210✔
1784
    {
1785
        CPLError(CE_Failure, CPLE_AssertionFailed,
×
1786
                 "CommitObjAndCoordBlocks() failed: file not opened for write "
1787
                 "access.");
1788
        return -1;
×
1789
    }
1790

1791
    if (!m_bLastOpWasWrite)
12,210✔
1792
    {
1793
        if (bDeleteObjects)
1,136✔
1794
        {
1795
            delete m_poCurCoordBlock;
1,098✔
1796
            m_poCurCoordBlock = nullptr;
1,098✔
1797
            delete m_poCurObjBlock;
1,098✔
1798
            m_poCurObjBlock = nullptr;
1,098✔
1799
        }
1800
        return 0;
1,136✔
1801
    }
1802
    m_bLastOpWasWrite = FALSE;
11,074✔
1803

1804
    /*-----------------------------------------------------------------
1805
     * We need to flush the coord block if there was one
1806
     * since a list of coord blocks can belong to only one obj. block
1807
     *----------------------------------------------------------------*/
1808
    if (m_poCurCoordBlock)
11,074✔
1809
    {
1810
        // Update the m_nMaxCoordBufSize member in the header block
1811
        //
1812
        int nTotalCoordSize = m_poCurCoordBlock->GetNumBlocksInChain() *
207✔
1813
                              m_poHeader->m_nRegularBlockSize;
207✔
1814
        if (nTotalCoordSize > m_poHeader->m_nMaxCoordBufSize)
207✔
1815
            m_poHeader->m_nMaxCoordBufSize = nTotalCoordSize;
35✔
1816

1817
        // Update the references to this coord block in the MAPObjBlock
1818
        //
1819
        m_poCurObjBlock->AddCoordBlockRef(m_poCurCoordBlock->GetStartAddress());
207✔
1820
        nStatus = m_poCurCoordBlock->CommitToFile();
207✔
1821

1822
        if (bDeleteObjects)
207✔
1823
        {
1824
            delete m_poCurCoordBlock;
66✔
1825
            m_poCurCoordBlock = nullptr;
66✔
1826
        }
1827
    }
1828

1829
    /*-----------------------------------------------------------------
1830
     * Commit the obj block
1831
     *----------------------------------------------------------------*/
1832
    if (nStatus == 0)
11,074✔
1833
    {
1834
        nStatus = m_poCurObjBlock->CommitToFile();
11,074✔
1835
    }
1836

1837
    /*-----------------------------------------------------------------
1838
     * Update the spatial index ** only in "quick spatial index" mode **
1839
     * In the (default) optimized spatial index mode, the spatial index
1840
     * is already maintained up to date as part of inserting the objects in
1841
     * PrepareNewObj().
1842
     *
1843
     * Spatial index will be created here if it was not done yet.
1844
     *----------------------------------------------------------------*/
1845
    if (nStatus == 0 && m_bQuickSpatialIndexMode)
11,074✔
1846
    {
1847
        if (m_poSpIndex == nullptr)
205✔
1848
        {
1849
            // Spatial Index not created yet...
1850
            m_poSpIndex = new TABMAPIndexBlock(m_eAccessMode);
87✔
1851

1852
            m_poSpIndex->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
87✔
1853
                                      m_oBlockManager.AllocNewBlock("INDEX"));
1854
            m_poSpIndex->SetMAPBlockManagerRef(&m_oBlockManager);
87✔
1855

1856
            m_poHeader->m_nFirstIndexBlock = m_poSpIndex->GetNodeBlockPtr();
87✔
1857
        }
1858

1859
        GInt32 nXMin, nYMin, nXMax, nYMax;
1860
        m_poCurObjBlock->GetMBR(nXMin, nYMin, nXMax, nYMax);
205✔
1861
        nStatus = m_poSpIndex->AddEntry(nXMin, nYMin, nXMax, nYMax,
205✔
1862
                                        m_poCurObjBlock->GetStartAddress());
205✔
1863

1864
        const int nNextDepth = m_poSpIndex->GetCurMaxDepth() + 1;
205✔
1865
        m_poHeader->m_nMaxSpIndexDepth = static_cast<GByte>(std::max(
205✔
1866
            static_cast<int>(m_poHeader->m_nMaxSpIndexDepth), nNextDepth));
205✔
1867
    }
1868

1869
    /*-----------------------------------------------------------------
1870
     * Delete obj block only if requested
1871
     *----------------------------------------------------------------*/
1872
    if (bDeleteObjects)
11,074✔
1873
    {
1874
        delete m_poCurObjBlock;
9,622✔
1875
        m_poCurObjBlock = nullptr;
9,622✔
1876
    }
1877

1878
    return nStatus;
11,074✔
1879
}
1880

1881
/**********************************************************************
1882
 *                   TABMAPFile::LoadObjAndCoordBlocks()
1883
 *
1884
 * Load the TABMAPObjBlock at specified address and corresponding
1885
 * TABMAPCoordBlock, ready to write new objects to them.
1886
 *
1887
 * It is assumed that pre-existing m_poCurObjBlock and m_poCurCoordBlock
1888
 * have been flushed to disk already using CommitObjAndCoordBlocks()
1889
 *
1890
 * Returns 0 on success, -1 on error.
1891
 **********************************************************************/
1892
int TABMAPFile::LoadObjAndCoordBlocks(GInt32 nBlockPtr)
10,720✔
1893
{
1894
    /*-----------------------------------------------------------------
1895
     * In Write mode, if an object block is already in memory then flush it
1896
     *----------------------------------------------------------------*/
1897
    if (m_eAccessMode != TABRead && m_poCurObjBlock != nullptr)
10,720✔
1898
    {
1899
        int nStatus = CommitObjAndCoordBlocks(TRUE);
×
1900
        if (nStatus != 0)
×
1901
            return nStatus;
×
1902
    }
1903

1904
    /*-----------------------------------------------------------------
1905
     * Load Obj Block
1906
     *----------------------------------------------------------------*/
1907
    TABRawBinBlock *poBlock = TABCreateMAPBlockFromFile(
21,440✔
1908
        m_fp, nBlockPtr, m_poHeader->m_nRegularBlockSize, TRUE, TABReadWrite);
10,720✔
1909
    if (poBlock != nullptr && poBlock->GetBlockClass() == TABMAP_OBJECT_BLOCK)
10,720✔
1910
    {
1911
        m_poCurObjBlock = cpl::down_cast<TABMAPObjectBlock *>(poBlock);
10,720✔
1912
        poBlock = nullptr;
10,720✔
1913
    }
1914
    else
1915
    {
1916
        CPLError(CE_Failure, CPLE_FileIO,
×
1917
                 "LoadObjAndCoordBlocks() failed for object block at %d.",
1918
                 nBlockPtr);
1919
        return -1;
×
1920
    }
1921

1922
    /*-----------------------------------------------------------------
1923
     * Load the last coord block in the chain
1924
     *----------------------------------------------------------------*/
1925
    if (m_poCurObjBlock->GetLastCoordBlockAddress() == 0)
10,720✔
1926
    {
1927
        m_poCurCoordBlock = nullptr;
10,562✔
1928
        return 0;
10,562✔
1929
    }
1930

1931
    poBlock = TABCreateMAPBlockFromFile(
158✔
1932
        m_fp, m_poCurObjBlock->GetLastCoordBlockAddress(),
158✔
1933
        m_poHeader->m_nRegularBlockSize, TRUE, TABReadWrite);
158✔
1934
    if (poBlock != nullptr && poBlock->GetBlockClass() == TABMAP_COORD_BLOCK)
158✔
1935
    {
1936
        m_poCurCoordBlock = cpl::down_cast<TABMAPCoordBlock *>(poBlock);
158✔
1937
        m_poCurCoordBlock->SetMAPBlockManagerRef(&m_oBlockManager);
158✔
1938
        poBlock = nullptr;
158✔
1939
    }
1940
    else
1941
    {
1942
        CPLError(CE_Failure, CPLE_FileIO,
×
1943
                 "LoadObjAndCoordBlocks() failed for coord block at %d.",
1944
                 m_poCurObjBlock->GetLastCoordBlockAddress());
×
1945
        return -1;
×
1946
    }
1947

1948
    return 0;
158✔
1949
}
1950

1951
/**********************************************************************
1952
 *                   TABMAPFile::SplitObjBlock()
1953
 *
1954
 * Split m_poCurObjBlock using Guttman algorithm.
1955
 *
1956
 * SplitObjBlock() doe its job so that the current obj block will remain
1957
 * the best candidate to receive the new object to add. It also flushes
1958
 * everything to disk and will update m_poCurCoordBlock to point to the
1959
 * last coord block in the chain, ready to accept new data
1960
 *
1961
 * Updates to the spatial index are left to the caller.
1962
 *
1963
 * Returns the TABMAPObjBlock of the second block for use by the caller
1964
 * in updating the spatial index, or NULL in case of error.
1965
 **********************************************************************/
1966
TABMAPObjectBlock *TABMAPFile::SplitObjBlock(TABMAPObjHdr *poObjHdrToAdd,
240✔
1967
                                             int nSizeOfObjToAdd)
1968
{
1969
    std::vector<std::unique_ptr<TABMAPObjHdr>> apoSrcObjHdrs;
480✔
1970

1971
    /*-----------------------------------------------------------------
1972
     * Read all object headers
1973
     *----------------------------------------------------------------*/
1974
    m_poCurObjBlock->Rewind();
240✔
1975
    while (auto poObjHdr =
1976
               TABMAPObjHdr::ReadNextObj(m_poCurObjBlock, m_poHeader))
8,528✔
1977
    {
1978
        apoSrcObjHdrs.emplace_back(poObjHdr);
8,288✔
1979
    }
8,288✔
1980
    /* PickSeedsForSplit (reasonably) assumes at least 2 nodes */
1981
    CPLAssert(apoSrcObjHdrs.size() > 1);
240✔
1982

1983
    /*-----------------------------------------------------------------
1984
     * Reset current obj and coord block
1985
     *----------------------------------------------------------------*/
1986
    GInt32 nFirstSrcCoordBlock = m_poCurObjBlock->GetFirstCoordBlockAddress();
240✔
1987

1988
    m_poCurObjBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
240✔
1989
                                  m_poCurObjBlock->GetStartAddress());
240✔
1990

1991
    std::unique_ptr<TABMAPCoordBlock> poSrcCoordBlock(m_poCurCoordBlock);
480✔
1992
    m_poCurCoordBlock = nullptr;
240✔
1993

1994
    /*-----------------------------------------------------------------
1995
     * Create new obj and coord block
1996
     *----------------------------------------------------------------*/
1997
    auto poNewObjBlock = std::make_unique<TABMAPObjectBlock>(m_eAccessMode);
480✔
1998
    poNewObjBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
240✔
1999
                                m_oBlockManager.AllocNewBlock("OBJECT"));
2000

2001
    /* Use existing center of other block in case we have compressed objects
2002
       and freeze it */
2003
    poNewObjBlock->SetCenterFromOtherBlock(m_poCurObjBlock);
240✔
2004

2005
    /* Coord block will be alloc'd automatically*/
2006
    TABMAPCoordBlock *poNewCoordBlock = nullptr;
240✔
2007

2008
    /*-----------------------------------------------------------------
2009
     * Pick Seeds for each block
2010
     *----------------------------------------------------------------*/
2011
    std::vector<TABMAPIndexEntry> asSrcEntries;
480✔
2012
    asSrcEntries.reserve(apoSrcObjHdrs.size());
240✔
2013
    for (const auto &poSrcObjHdrs : apoSrcObjHdrs)
8,528✔
2014
    {
2015
        TABMAPIndexEntry sEntry;
2016
        sEntry.nBlockPtr = 0;
8,288✔
2017
        sEntry.XMin = poSrcObjHdrs->m_nMinX;
8,288✔
2018
        sEntry.YMin = poSrcObjHdrs->m_nMinY;
8,288✔
2019
        sEntry.XMax = poSrcObjHdrs->m_nMaxX;
8,288✔
2020
        sEntry.YMax = poSrcObjHdrs->m_nMaxY;
8,288✔
2021
        asSrcEntries.emplace_back(sEntry);
8,288✔
2022
    }
2023

2024
    int nSeed1, nSeed2;
2025
    TABMAPIndexBlock::PickSeedsForSplit(
240✔
2026
        asSrcEntries.data(), static_cast<int>(asSrcEntries.size()), -1,
240✔
2027
        poObjHdrToAdd->m_nMinX, poObjHdrToAdd->m_nMinY, poObjHdrToAdd->m_nMaxX,
2028
        poObjHdrToAdd->m_nMaxY, nSeed1, nSeed2);
2029

2030
    /*-----------------------------------------------------------------
2031
     * Assign the seeds to their respective block
2032
     *----------------------------------------------------------------*/
2033
    // Insert nSeed1 in this block
2034
    if (MoveObjToBlock(apoSrcObjHdrs[nSeed1].get(), poSrcCoordBlock.get(),
240✔
2035
                       m_poCurObjBlock, &m_poCurCoordBlock) <= 0)
240✔
2036
    {
2037
        return nullptr;
×
2038
    }
2039

2040
    // Move nSeed2 to 2nd block
2041
    if (MoveObjToBlock(apoSrcObjHdrs[nSeed2].get(), poSrcCoordBlock.get(),
240✔
2042
                       poNewObjBlock.get(), &poNewCoordBlock) <= 0)
240✔
2043
    {
2044
        return nullptr;
×
2045
    }
2046

2047
    /*-----------------------------------------------------------------
2048
     * Go through the rest of the entries and assign them to one
2049
     * of the 2 blocks
2050
     *
2051
     * Criteria is minimal area difference.
2052
     * Resolve ties by adding the entry to the block with smaller total
2053
     * area, then to the one with fewer entries, then to either.
2054
     *----------------------------------------------------------------*/
2055
    for (int iEntry = 0; iEntry < static_cast<int>(apoSrcObjHdrs.size());
8,528✔
2056
         iEntry++)
2057
    {
2058
        if (iEntry == nSeed1 || iEntry == nSeed2)
8,288✔
2059
            continue;
480✔
2060

2061
        TABMAPObjHdr *poObjHdr = apoSrcObjHdrs[iEntry].get();
7,808✔
2062

2063
        int nObjSize = m_poHeader->GetMapObjectSize(poObjHdr->m_nType);
7,808✔
2064

2065
        // If one of the two blocks is almost full then all remaining
2066
        // entries should go to the other block
2067
        if (m_poCurObjBlock->GetNumUnusedBytes() < nObjSize + nSizeOfObjToAdd)
7,808✔
2068
        {
2069
            if (MoveObjToBlock(poObjHdr, poSrcCoordBlock.get(),
×
2070
                               poNewObjBlock.get(), &poNewCoordBlock) <= 0)
×
2071
                return nullptr;
×
2072
            continue;
×
2073
        }
2074
        else if (poNewObjBlock->GetNumUnusedBytes() <
7,808✔
2075
                 nObjSize + nSizeOfObjToAdd)
7,808✔
2076
        {
2077
            if (MoveObjToBlock(poObjHdr, poSrcCoordBlock.get(), m_poCurObjBlock,
×
2078
                               &m_poCurCoordBlock) <= 0)
×
2079
                return nullptr;
×
2080
            continue;
×
2081
        }
2082

2083
        // Decide which of the two blocks to put this entry in
2084
        GInt32 nXMin, nYMin, nXMax, nYMax;
2085
        m_poCurObjBlock->GetMBR(nXMin, nYMin, nXMax, nYMax);
7,808✔
2086
        CPLAssert(nXMin <= nXMax);
7,808✔
2087
        double dAreaDiff1 = TABMAPIndexBlock::ComputeAreaDiff(
7,808✔
2088
            nXMin, nYMin, nXMax, nYMax, poObjHdr->m_nMinX, poObjHdr->m_nMinY,
2089
            poObjHdr->m_nMaxX, poObjHdr->m_nMaxY);
2090

2091
        poNewObjBlock->GetMBR(nXMin, nYMin, nXMax, nYMax);
7,808✔
2092
        CPLAssert(nXMin <= nXMax);
7,808✔
2093
        double dAreaDiff2 = TABMAPIndexBlock::ComputeAreaDiff(
7,808✔
2094
            nXMin, nYMin, nXMax, nYMax, poObjHdr->m_nMinX, poObjHdr->m_nMinY,
2095
            poObjHdr->m_nMaxX, poObjHdr->m_nMaxY);
2096

2097
        if (dAreaDiff1 < dAreaDiff2)
7,808✔
2098
        {
2099
            // This entry stays in this block
2100
            if (MoveObjToBlock(poObjHdr, poSrcCoordBlock.get(), m_poCurObjBlock,
3,153✔
2101
                               &m_poCurCoordBlock) <= 0)
3,153✔
2102
                return nullptr;
×
2103
        }
2104
        else
2105
        {
2106
            // This entry goes to new block
2107
            if (MoveObjToBlock(poObjHdr, poSrcCoordBlock.get(),
4,655✔
2108
                               poNewObjBlock.get(), &poNewCoordBlock) <= 0)
4,655✔
2109
                return nullptr;
×
2110
        }
2111
    }
2112

2113
    /*-----------------------------------------------------------------
2114
     * Delete second coord block if one was created
2115
     * Refs to coord block were kept up to date by MoveObjToBlock()
2116
     * We just need to commit to file and delete the object now.
2117
     *----------------------------------------------------------------*/
2118
    if (poNewCoordBlock)
240✔
2119
    {
2120
        if (poNewCoordBlock->CommitToFile() != 0)
8✔
2121
        {
2122
            return nullptr;
×
2123
        }
2124
        delete poNewCoordBlock;
8✔
2125
    }
2126

2127
    /*-----------------------------------------------------------------
2128
     * Release unused coord. data blocks
2129
     *----------------------------------------------------------------*/
2130
    if (poSrcCoordBlock)
240✔
2131
    {
2132
        if (poSrcCoordBlock->GetStartAddress() != nFirstSrcCoordBlock)
8✔
2133
        {
2134
            if (poSrcCoordBlock->GotoByteInFile(nFirstSrcCoordBlock, TRUE) != 0)
×
2135
            {
2136
                return nullptr;
×
2137
            }
2138
        }
2139

2140
        int nNextCoordBlock = poSrcCoordBlock->GetNextCoordBlock();
8✔
2141
        while (poSrcCoordBlock != nullptr)
16✔
2142
        {
2143
            // Mark this block as deleted
2144
            if (poSrcCoordBlock->CommitAsDeleted(
8✔
2145
                    m_oBlockManager.GetFirstGarbageBlock()) != 0)
8✔
2146
            {
2147
                return nullptr;
×
2148
            }
2149
            m_oBlockManager.PushGarbageBlockAsFirst(
8✔
2150
                poSrcCoordBlock->GetStartAddress());
8✔
2151

2152
            // Advance to next
2153
            if (nNextCoordBlock > 0)
8✔
2154
            {
2155
                if (poSrcCoordBlock->GotoByteInFile(nNextCoordBlock, TRUE) != 0)
×
2156
                    return nullptr;
×
2157

2158
                nNextCoordBlock = poSrcCoordBlock->GetNextCoordBlock();
×
2159
            }
2160
            else
2161
            {
2162
                // end of chain
2163
                poSrcCoordBlock.reset();
8✔
2164
            }
2165
        }
2166
    }
2167

2168
    if (poNewObjBlock->CommitToFile() != 0)
240✔
2169
        return nullptr;
×
2170

2171
    return poNewObjBlock.release();
240✔
2172
}
2173

2174
/**********************************************************************
2175
 *                   TABMAPFile::MoveObjToBlock()
2176
 *
2177
 * Moves an object and its coord data to a new ObjBlock. Used when
2178
 * splitting Obj Blocks.
2179
 *
2180
 * May update the value of ppoCoordBlock if a new coord block had to
2181
 * be created.
2182
 *
2183
 * Returns the address where new object is stored on success, -1 on error.
2184
 **********************************************************************/
2185
int TABMAPFile::MoveObjToBlock(TABMAPObjHdr *poObjHdr,
8,288✔
2186
                               TABMAPCoordBlock *poSrcCoordBlock,
2187
                               TABMAPObjectBlock *poDstObjBlock,
2188
                               TABMAPCoordBlock **ppoDstCoordBlock)
2189
{
2190
    /*-----------------------------------------------------------------
2191
     * Copy Coord data if applicable
2192
     * We use a temporary TABFeature object to handle the reading/writing
2193
     * of coord block data.
2194
     *----------------------------------------------------------------*/
2195
    if (m_poHeader->MapObjectUsesCoordBlock(poObjHdr->m_nType))
8,288✔
2196
    {
2197
        TABMAPObjHdrWithCoord *poObjHdrCoord =
2198
            cpl::down_cast<TABMAPObjHdrWithCoord *>(poObjHdr);
112✔
2199
        OGRFeatureDefn *poDummyDefn = new OGRFeatureDefn;
112✔
2200
        // Ref count defaults to 0... set it to 1
2201
        poDummyDefn->Reference();
112✔
2202

2203
        TABFeature *poFeature =
2204
            TABFeature::CreateFromMapInfoType(poObjHdr->m_nType, poDummyDefn);
112✔
2205

2206
        if (PrepareCoordBlock(poObjHdrCoord->m_nType, poDstObjBlock,
112✔
2207
                              ppoDstCoordBlock) != 0)
112✔
2208
            return -1;
×
2209

2210
        GInt32 nSrcCoordPtr = poObjHdrCoord->m_nCoordBlockPtr;
112✔
2211

2212
        /* Copy Coord data
2213
         * poObjHdrCoord->m_nCoordBlockPtr will be set by WriteGeometry...
2214
         * We pass second arg to GotoByteInFile() to force reading from file
2215
         * if nSrcCoordPtr is not in current block
2216
         */
2217
        if (poSrcCoordBlock->GotoByteInFile(nSrcCoordPtr, TRUE) != 0 ||
112✔
2218
            poFeature->ReadGeometryFromMAPFile(this, poObjHdr,
112✔
2219
                                               TRUE /* bCoordDataOnly */,
2220
                                               &poSrcCoordBlock) != 0 ||
224✔
2221
            poFeature->WriteGeometryToMAPFile(this, poObjHdr,
112✔
2222
                                              TRUE /* bCoordDataOnly */,
2223
                                              ppoDstCoordBlock) != 0)
112✔
2224
        {
2225
            delete poFeature;
×
2226
            delete poDummyDefn;
×
2227
            return -1;
×
2228
        }
2229

2230
        // Update the references to dest coord block in the MAPObjBlock
2231
        // in case new block has been alloc'd since PrepareCoordBlock()
2232
        //
2233
        poDstObjBlock->AddCoordBlockRef((*ppoDstCoordBlock)->GetStartAddress());
112✔
2234
        /* Cleanup */
2235
        delete poFeature;
112✔
2236
        poDummyDefn->Release();
112✔
2237
    }
2238

2239
    /*-----------------------------------------------------------------
2240
     * Prepare and Write ObjHdr to this ObjBlock
2241
     *----------------------------------------------------------------*/
2242
    int nObjPtr = poDstObjBlock->PrepareNewObject(poObjHdr);
8,288✔
2243
    if (nObjPtr < 0 || poDstObjBlock->CommitNewObject(poObjHdr) != 0)
8,288✔
2244
    {
2245
        CPLError(CE_Failure, CPLE_FileIO,
×
2246
                 "Failed writing object header for feature id %d",
2247
                 poObjHdr->m_nId);
2248
        return -1;
×
2249
    }
2250

2251
    /*-----------------------------------------------------------------
2252
     * Update .ID Index
2253
     *----------------------------------------------------------------*/
2254
    m_poIdIndex->SetObjPtr(poObjHdr->m_nId, nObjPtr);
8,288✔
2255

2256
    return nObjPtr;
8,288✔
2257
}
2258

2259
/**********************************************************************
2260
 *                   TABMAPFile::PrepareCoordBlock()
2261
 *
2262
 * Prepare the coord block to receive an object of specified type if one
2263
 * is needed, and update corresponding members in ObjBlock.
2264
 *
2265
 * May update the value of ppoCoordBlock and Returns 0 on success, -1 on error.
2266
 **********************************************************************/
2267
int TABMAPFile::PrepareCoordBlock(int nObjType, TABMAPObjectBlock *poObjBlock,
15,133✔
2268
                                  TABMAPCoordBlock **ppoCoordBlock)
2269
{
2270

2271
    /*-----------------------------------------------------------------
2272
     * Prepare Coords block...
2273
     * create a new TABMAPCoordBlock if it was not done yet.
2274
     * Note that in write mode, TABCollections require read/write access
2275
     * to the coord block.
2276
     *----------------------------------------------------------------*/
2277
    if (m_poHeader->MapObjectUsesCoordBlock(nObjType))
15,133✔
2278
    {
2279
        if (*ppoCoordBlock == nullptr)
419✔
2280
        {
2281
            *ppoCoordBlock = new TABMAPCoordBlock(
51✔
2282
                m_eAccessMode == TABWrite ? TABReadWrite : m_eAccessMode);
51✔
2283
            (*ppoCoordBlock)
2284
                ->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
51✔
2285
                               m_oBlockManager.AllocNewBlock("COORD"));
2286
            (*ppoCoordBlock)->SetMAPBlockManagerRef(&m_oBlockManager);
51✔
2287

2288
            // Set the references to this coord block in the MAPObjBlock
2289
            poObjBlock->AddCoordBlockRef((*ppoCoordBlock)->GetStartAddress());
51✔
2290
        }
2291
        /* If we are not at the end of the chain of coordinate blocks, then */
2292
        /* reload us */
2293
        else if ((*ppoCoordBlock)->GetStartAddress() !=
736✔
2294
                 poObjBlock->GetLastCoordBlockAddress())
368✔
2295
        {
2296
            TABRawBinBlock *poBlock = TABCreateMAPBlockFromFile(
3✔
2297
                m_fp, poObjBlock->GetLastCoordBlockAddress(),
2298
                m_poHeader->m_nRegularBlockSize, TRUE, TABReadWrite);
3✔
2299
            if (poBlock != nullptr &&
6✔
2300
                poBlock->GetBlockClass() == TABMAP_COORD_BLOCK)
3✔
2301
            {
2302
                delete *ppoCoordBlock;
3✔
2303
                *ppoCoordBlock = cpl::down_cast<TABMAPCoordBlock *>(poBlock);
3✔
2304
                (*ppoCoordBlock)->SetMAPBlockManagerRef(&m_oBlockManager);
3✔
2305
            }
2306
            else
2307
            {
2308
                delete poBlock;
×
2309
                CPLError(
×
2310
                    CE_Failure, CPLE_FileIO,
2311
                    "LoadObjAndCoordBlocks() failed for coord block at %d.",
2312
                    poObjBlock->GetLastCoordBlockAddress());
2313
                return -1;
×
2314
            }
2315
        }
2316

2317
        if ((*ppoCoordBlock)->GetNumUnusedBytes() < 4)
419✔
2318
        {
2319
            int nNewBlockOffset = m_oBlockManager.AllocNewBlock("COORD");
×
2320
            (*ppoCoordBlock)->SetNextCoordBlock(nNewBlockOffset);
×
2321
            CPL_IGNORE_RET_VAL((*ppoCoordBlock)->CommitToFile());
×
2322
            (*ppoCoordBlock)
2323
                ->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
×
2324
                               nNewBlockOffset);
2325
            poObjBlock->AddCoordBlockRef((*ppoCoordBlock)->GetStartAddress());
×
2326
        }
2327

2328
        // Make sure read/write pointer is at the end of the block
2329
        (*ppoCoordBlock)->SeekEnd();
419✔
2330

2331
        if (CPLGetLastErrorType() == CE_Failure)
419✔
2332
            return -1;
×
2333
    }
2334

2335
    return 0;
15,133✔
2336
}
2337

2338
/**********************************************************************
2339
 *                   TABMAPFile::GetCurObjType()
2340
 *
2341
 * Return the MapInfo object type of the object that the m_poCurObjBlock
2342
 * is pointing to.  This value is set after a call to MoveToObjId().
2343
 *
2344
 * Returns a value >= 0 on success, -1 on error.
2345
 **********************************************************************/
2346
TABGeomType TABMAPFile::GetCurObjType()
1,250,010✔
2347
{
2348
    return m_nCurObjType;
1,250,010✔
2349
}
2350

2351
/**********************************************************************
2352
 *                   TABMAPFile::GetCurObjId()
2353
 *
2354
 * Return the MapInfo object id of the object that the m_poCurObjBlock
2355
 * is pointing to.  This value is set after a call to MoveToObjId().
2356
 *
2357
 * Returns a value >= 0 on success, -1 on error.
2358
 **********************************************************************/
2359
int TABMAPFile::GetCurObjId()
548,750✔
2360
{
2361
    return m_nCurObjId;
548,750✔
2362
}
2363

2364
/**********************************************************************
2365
 *                   TABMAPFile::GetCurObjBlock()
2366
 *
2367
 * Return the m_poCurObjBlock.  If MoveToObjId() has previously been
2368
 * called then m_poCurObjBlock points to the beginning of the current
2369
 * object data.
2370
 *
2371
 * Returns a reference to an object owned by this TABMAPFile object, or
2372
 * NULL on error.
2373
 **********************************************************************/
2374
TABMAPObjectBlock *TABMAPFile::GetCurObjBlock()
548,750✔
2375
{
2376
    return m_poCurObjBlock;
548,750✔
2377
}
2378

2379
/**********************************************************************
2380
 *                   TABMAPFile::GetCurCoordBlock()
2381
 *
2382
 * Return the m_poCurCoordBlock.  This function should be used after
2383
 * PrepareNewObj() to get the reference to the coord block that has
2384
 * just been initialized.
2385
 *
2386
 * Returns a reference to an object owned by this TABMAPFile object, or
2387
 * NULL on error.
2388
 **********************************************************************/
2389
TABMAPCoordBlock *TABMAPFile::GetCurCoordBlock()
307✔
2390
{
2391
    return m_poCurCoordBlock;
307✔
2392
}
2393

2394
/**********************************************************************
2395
 *                   TABMAPFile::GetCoordBlock()
2396
 *
2397
 * Return a TABMAPCoordBlock object ready to read coordinates from it.
2398
 * The block that contains nFileOffset will automatically be
2399
 * loaded, and if nFileOffset is the beginning of a new block then the
2400
 * pointer will be moved to the beginning of the data.
2401
 *
2402
 * The contents of the returned object is only valid until the next call
2403
 * to GetCoordBlock().
2404
 *
2405
 * Returns a reference to an object owned by this TABMAPFile object, or
2406
 * NULL on error.
2407
 **********************************************************************/
2408
TABMAPCoordBlock *TABMAPFile::GetCoordBlock(int nFileOffset)
2,026✔
2409
{
2410
    if (m_poCurCoordBlock == nullptr)
2,026✔
2411
    {
2412
        m_poCurCoordBlock = new TABMAPCoordBlock(m_eAccessMode);
22✔
2413
        m_poCurCoordBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize);
22✔
2414
        m_poCurCoordBlock->SetMAPBlockManagerRef(&m_oBlockManager);
22✔
2415
    }
2416

2417
    /*-----------------------------------------------------------------
2418
     * Use GotoByteInFile() to go to the requested location.  This will
2419
     * force loading the block if necessary and reading its header.
2420
     * If nFileOffset is at the beginning of the requested block, then
2421
     * we make sure to move the read pointer past the 8 bytes header
2422
     * to be ready to read coordinates data
2423
     *----------------------------------------------------------------*/
2424
    if (m_poCurCoordBlock->GotoByteInFile(nFileOffset, TRUE) != 0)
2,026✔
2425
    {
2426
        // Failed... an error has already been reported.
2427
        return nullptr;
×
2428
    }
2429

2430
    if (nFileOffset % m_poHeader->m_nRegularBlockSize == 0)
2,026✔
2431
        m_poCurCoordBlock->GotoByteInBlock(8);  // Skip Header
×
2432

2433
    return m_poCurCoordBlock;
2,026✔
2434
}
2435

2436
/**********************************************************************
2437
 *                   TABMAPFile::GetHeaderBlock()
2438
 *
2439
 * Return a reference to the MAP file's header block.
2440
 *
2441
 * The returned pointer is a reference to an object owned by this TABMAPFile
2442
 * object and should not be deleted by the caller.
2443
 *
2444
 * Return NULL if file has not been opened yet.
2445
 **********************************************************************/
2446
TABMAPHeaderBlock *TABMAPFile::GetHeaderBlock()
2,672✔
2447
{
2448
    return m_poHeader;
2,672✔
2449
}
2450

2451
/**********************************************************************
2452
 *                   TABMAPFile::GetIDFileRef()
2453
 *
2454
 * Return a reference to the .ID file attached to this .MAP file
2455
 *
2456
 * The returned pointer is a reference to an object owned by this TABMAPFile
2457
 * object and should not be deleted by the caller.
2458
 *
2459
 * Return NULL if file has not been opened yet.
2460
 **********************************************************************/
2461
TABIDFile *TABMAPFile::GetIDFileRef()
×
2462
{
2463
    return m_poIdIndex;
×
2464
}
2465

2466
/**********************************************************************
2467
 *                   TABMAPFile::GetIndexBlock()
2468
 *
2469
 * Return a reference to the requested index or object block..
2470
 *
2471
 * Ownership of the returned block is turned over to the caller, who should
2472
 * delete it when no longer needed.  The type of the block can be determined
2473
 * with the GetBlockType() method.
2474
 *
2475
 * @param nFileOffset the offset in the map file of the spatial index
2476
 * block or object block to load.
2477
 *
2478
 * @return The requested TABMAPIndexBlock, TABMAPObjectBlock or NULL if the
2479
 * read fails for some reason.
2480
 **********************************************************************/
2481
TABRawBinBlock *TABMAPFile::GetIndexObjectBlock(int nFileOffset)
42,458✔
2482
{
2483
    /*----------------------------------------------------------------
2484
     * Read from the file
2485
     *---------------------------------------------------------------*/
2486
    GByte *pabyData =
2487
        static_cast<GByte *>(CPLMalloc(m_poHeader->m_nRegularBlockSize));
42,458✔
2488

2489
    if (VSIFSeekL(m_fp, nFileOffset, SEEK_SET) != 0 ||
84,916✔
2490
        static_cast<int>(VSIFReadL(pabyData, sizeof(GByte),
84,916✔
2491
                                   m_poHeader->m_nRegularBlockSize, m_fp)) !=
42,458✔
2492
            m_poHeader->m_nRegularBlockSize)
42,458✔
2493
    {
2494
        CPLError(CE_Failure, CPLE_FileIO,
×
2495
                 "GetIndexBlock() failed reading %d bytes at offset %d.",
2496
                 m_poHeader->m_nRegularBlockSize, nFileOffset);
×
2497
        CPLFree(pabyData);
×
2498
        return nullptr;
×
2499
    }
2500

2501
    /* -------------------------------------------------------------------- */
2502
    /*      Create and initialize depending on the block type.              */
2503
    /* -------------------------------------------------------------------- */
2504
    int nBlockType = pabyData[0];
42,458✔
2505
    TABRawBinBlock *poBlock = nullptr;
42,458✔
2506

2507
    if (nBlockType == TABMAP_INDEX_BLOCK)
42,458✔
2508
    {
2509
        TABMAPIndexBlock *poIndexBlock = new TABMAPIndexBlock(m_eAccessMode);
26,856✔
2510
        poBlock = poIndexBlock;
26,856✔
2511
        poIndexBlock->SetMAPBlockManagerRef(&m_oBlockManager);
26,856✔
2512
    }
2513
    else
2514
        poBlock = new TABMAPObjectBlock(m_eAccessMode);
15,602✔
2515

2516
    poBlock->InitBlockFromData(pabyData, m_poHeader->m_nRegularBlockSize,
42,458✔
2517
                               m_poHeader->m_nRegularBlockSize, FALSE, m_fp,
42,458✔
2518
                               nFileOffset);
42,458✔
2519

2520
    return poBlock;
42,458✔
2521
}
2522

2523
/**********************************************************************
2524
 *                   TABMAPFile::InitDrawingTools()
2525
 *
2526
 * Init the drawing tools for this file.
2527
 *
2528
 * In Read mode, this will load the drawing tools from the file.
2529
 *
2530
 * In Write mode, this function will init an empty the tool def table.
2531
 *
2532
 * Returns 0 on success, -1 on error.
2533
 **********************************************************************/
2534
int TABMAPFile::InitDrawingTools()
1,325✔
2535
{
2536
    int nStatus = 0;
1,325✔
2537

2538
    if (m_poHeader == nullptr)
1,325✔
2539
        return -1;  // File not opened yet!
×
2540

2541
    /*-------------------------------------------------------------
2542
     * We want to perform this initialization only once
2543
     *------------------------------------------------------------*/
2544
    if (m_poToolDefTable != nullptr)
1,325✔
2545
        return 0;
×
2546

2547
    /*-------------------------------------------------------------
2548
     * Create a new ToolDefTable... no more initialization is required
2549
     * unless we want to read tool blocks from file.
2550
     *------------------------------------------------------------*/
2551
    m_poToolDefTable = new TABToolDefTable;
1,325✔
2552

2553
    if ((m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite) &&
1,325✔
2554
        m_poHeader->m_nFirstToolBlock != 0)
1,238✔
2555
    {
2556
        TABMAPToolBlock *poBlock = new TABMAPToolBlock(TABRead);
1,211✔
2557
        poBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize);
1,211✔
2558

2559
        /*-------------------------------------------------------------
2560
         * Use GotoByteInFile() to go to the first block's location.  This will
2561
         * force loading the block if necessary and reading its header.
2562
         * Also make sure to move the read pointer past the 8 bytes header
2563
         * to be ready to read drawing tools data
2564
         *------------------------------------------------------------*/
2565
        if (poBlock->GotoByteInFile(m_poHeader->m_nFirstToolBlock) != 0)
1,211✔
2566
        {
2567
            // Failed... an error has already been reported.
2568
            delete poBlock;
×
2569
            return -1;
×
2570
        }
2571

2572
        poBlock->GotoByteInBlock(8);
1,211✔
2573

2574
        nStatus = m_poToolDefTable->ReadAllToolDefs(poBlock);
1,211✔
2575
        delete poBlock;
1,211✔
2576
    }
2577

2578
    return nStatus;
1,325✔
2579
}
2580

2581
/**********************************************************************
2582
 *                   TABMAPFile::CommitDrawingTools()
2583
 *
2584
 * Write the drawing tools for this file.
2585
 *
2586
 * This function applies only to write access mode.
2587
 *
2588
 * Returns 0 on success, -1 on error.
2589
 **********************************************************************/
2590
int TABMAPFile::CommitDrawingTools()
1,201✔
2591
{
2592
    int nStatus = 0;
1,201✔
2593

2594
    if (m_eAccessMode == TABRead || m_poHeader == nullptr)
1,201✔
2595
    {
2596
        CPLError(
×
2597
            CE_Failure, CPLE_AssertionFailed,
2598
            "CommitDrawingTools() failed: file not opened for write access.");
2599
        return -1;
×
2600
    }
2601

2602
    if (m_poToolDefTable == nullptr ||
2,366✔
2603
        (m_poToolDefTable->GetNumPen() + m_poToolDefTable->GetNumBrushes() +
1,165✔
2604
         m_poToolDefTable->GetNumFonts() + m_poToolDefTable->GetNumSymbols()) ==
1,165✔
2605
            0)
2606
    {
2607
        return 0;  // Nothing to do!
36✔
2608
    }
2609

2610
    /*-------------------------------------------------------------
2611
     * Create a new TABMAPToolBlock and update header fields
2612
     *------------------------------------------------------------*/
2613
    TABMAPToolBlock *poBlock = new TABMAPToolBlock(m_eAccessMode);
1,165✔
2614
    if (m_poHeader->m_nFirstToolBlock != 0)
1,165✔
2615
        poBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
1,075✔
2616
                              m_poHeader->m_nFirstToolBlock);
1,075✔
2617
    else
2618
        poBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
90✔
2619
                              m_oBlockManager.AllocNewBlock("TOOL"));
2620
    poBlock->SetMAPBlockManagerRef(&m_oBlockManager);
1,165✔
2621

2622
    m_poHeader->m_nFirstToolBlock = poBlock->GetStartAddress();
1,165✔
2623

2624
    m_poHeader->m_numPenDefs =
1,165✔
2625
        static_cast<GByte>(m_poToolDefTable->GetNumPen());
1,165✔
2626
    m_poHeader->m_numBrushDefs =
1,165✔
2627
        static_cast<GByte>(m_poToolDefTable->GetNumBrushes());
1,165✔
2628
    m_poHeader->m_numFontDefs =
1,165✔
2629
        static_cast<GByte>(m_poToolDefTable->GetNumFonts());
1,165✔
2630
    m_poHeader->m_numSymbolDefs =
1,165✔
2631
        static_cast<GByte>(m_poToolDefTable->GetNumSymbols());
1,165✔
2632

2633
    /*-------------------------------------------------------------
2634
     * Do the actual work and delete poBlock
2635
     * (Note that poBlock will have already been committed to the file
2636
     * by WriteAllToolDefs() )
2637
     *------------------------------------------------------------*/
2638
    nStatus = m_poToolDefTable->WriteAllToolDefs(poBlock);
1,165✔
2639

2640
    m_poHeader->m_numMapToolBlocks =
1,165✔
2641
        static_cast<GByte>(poBlock->GetNumBlocksInChain());
1,165✔
2642

2643
    delete poBlock;
1,165✔
2644

2645
    return nStatus;
1,165✔
2646
}
2647

2648
/**********************************************************************
2649
 *                   TABMAPFile::ReadPenDef()
2650
 *
2651
 * Fill the TABPenDef structure with the definition of the specified pen
2652
 * index... (1-based pen index)
2653
 *
2654
 * If nPenIndex==0 or is invalid, then the structure is cleared.
2655
 *
2656
 * Returns 0 on success, -1 on error (i.e. Pen not found).
2657
 **********************************************************************/
2658
int TABMAPFile::ReadPenDef(int nPenIndex, TABPenDef *psDef)
2,067✔
2659
{
2660
    if (m_poToolDefTable == nullptr && InitDrawingTools() != 0)
2,067✔
2661
        return -1;
×
2662

2663
    TABPenDef *psTmp = nullptr;
2,067✔
2664
    if (psDef && m_poToolDefTable &&
4,134✔
2665
        (psTmp = m_poToolDefTable->GetPenDefRef(nPenIndex)) != nullptr)
2,067✔
2666
    {
2667
        *psDef = *psTmp;
2,065✔
2668
    }
2669
    else if (psDef)
2✔
2670
    {
2671
        /* Init to MapInfo default */
2672
        static const TABPenDef csDefaultPen = MITAB_PEN_DEFAULT;
2673
        *psDef = csDefaultPen;
2✔
2674
        return -1;
2✔
2675
    }
2676
    return 0;
2,065✔
2677
}
2678

2679
/**********************************************************************
2680
 *                   TABMAPFile::WritePenDef()
2681
 *
2682
 * Write a Pen Tool to the map file and return the pen index that has
2683
 * been attributed to this Pen tool definition, or -1 if something went
2684
 * wrong
2685
 *
2686
 * Note that the returned index is a 1-based index.  A value of 0
2687
 * indicates "none" in MapInfo.
2688

2689
 * Returns a value >= 0 on success, -1 on error
2690
 **********************************************************************/
2691
int TABMAPFile::WritePenDef(TABPenDef *psDef)
330✔
2692
{
2693
    if (psDef == nullptr ||
660✔
2694
        (m_poToolDefTable == nullptr && InitDrawingTools() != 0) ||
660✔
2695
        m_poToolDefTable == nullptr)
330✔
2696
    {
2697
        return -1;
×
2698
    }
2699

2700
    return m_poToolDefTable->AddPenDefRef(psDef);
330✔
2701
}
2702

2703
/**********************************************************************
2704
 *                   TABMAPFile::ReadBrushDef()
2705
 *
2706
 * Fill the TABBrushDef structure with the definition of the specified Brush
2707
 * index... (1-based Brush index)
2708
 *
2709
 * If nBrushIndex==0 or is invalid, then the structure is cleared.
2710
 *
2711
 * Returns 0 on success, -1 on error (i.e. Brush not found).
2712
 **********************************************************************/
2713
int TABMAPFile::ReadBrushDef(int nBrushIndex, TABBrushDef *psDef)
526✔
2714
{
2715
    if (m_poToolDefTable == nullptr && InitDrawingTools() != 0)
526✔
2716
        return -1;
×
2717

2718
    TABBrushDef *psTmp = nullptr;
526✔
2719
    if (psDef && m_poToolDefTable &&
1,052✔
2720
        (psTmp = m_poToolDefTable->GetBrushDefRef(nBrushIndex)) != nullptr)
526✔
2721
    {
2722
        *psDef = *psTmp;
521✔
2723
    }
2724
    else if (psDef)
5✔
2725
    {
2726
        /* Init to MapInfo default */
2727
        static const TABBrushDef csDefaultBrush = MITAB_BRUSH_DEFAULT;
2728
        *psDef = csDefaultBrush;
5✔
2729
        return -1;
5✔
2730
    }
2731
    return 0;
521✔
2732
}
2733

2734
/**********************************************************************
2735
 *                   TABMAPFile::WriteBrushDef()
2736
 *
2737
 * Write a Brush Tool to the map file and return the Brush index that has
2738
 * been attributed to this Brush tool definition, or -1 if something went
2739
 * wrong
2740
 *
2741
 * Note that the returned index is a 1-based index.  A value of 0
2742
 * indicates "none" in MapInfo.
2743

2744
 * Returns a value >= 0 on success, -1 on error
2745
 **********************************************************************/
2746
int TABMAPFile::WriteBrushDef(TABBrushDef *psDef)
88✔
2747
{
2748
    if (psDef == nullptr ||
176✔
2749
        (m_poToolDefTable == nullptr && InitDrawingTools() != 0) ||
176✔
2750
        m_poToolDefTable == nullptr)
88✔
2751
    {
2752
        return -1;
×
2753
    }
2754

2755
    return m_poToolDefTable->AddBrushDefRef(psDef);
88✔
2756
}
2757

2758
/**********************************************************************
2759
 *                   TABMAPFile::ReadFontDef()
2760
 *
2761
 * Fill the TABFontDef structure with the definition of the specified Font
2762
 * index... (1-based Font index)
2763
 *
2764
 * If nFontIndex==0 or is invalid, then the structure is cleared.
2765
 *
2766
 * Returns 0 on success, -1 on error (i.e. Font not found).
2767
 **********************************************************************/
2768
int TABMAPFile::ReadFontDef(int nFontIndex, TABFontDef *psDef)
20✔
2769
{
2770
    if (m_poToolDefTable == nullptr && InitDrawingTools() != 0)
20✔
2771
        return -1;
×
2772

2773
    TABFontDef *psTmp = nullptr;
20✔
2774
    if (psDef && m_poToolDefTable &&
40✔
2775
        (psTmp = m_poToolDefTable->GetFontDefRef(nFontIndex)) != nullptr)
20✔
2776
    {
2777
        *psDef = *psTmp;
20✔
2778
    }
2779
    else if (psDef)
×
2780
    {
2781
        /* Init to MapInfo default */
2782
        static const TABFontDef csDefaultFont = MITAB_FONT_DEFAULT;
2783
        *psDef = csDefaultFont;
×
2784
        return -1;
×
2785
    }
2786
    return 0;
20✔
2787
}
2788

2789
/**********************************************************************
2790
 *                   TABMAPFile::WriteFontDef()
2791
 *
2792
 * Write a Font Tool to the map file and return the Font index that has
2793
 * been attributed to this Font tool definition, or -1 if something went
2794
 * wrong
2795
 *
2796
 * Note that the returned index is a 1-based index.  A value of 0
2797
 * indicates "none" in MapInfo.
2798

2799
 * Returns a value >= 0 on success, -1 on error
2800
 **********************************************************************/
2801
int TABMAPFile::WriteFontDef(TABFontDef *psDef)
8✔
2802
{
2803
    if (psDef == nullptr ||
16✔
2804
        (m_poToolDefTable == nullptr && InitDrawingTools() != 0) ||
16✔
2805
        m_poToolDefTable == nullptr)
8✔
2806
    {
2807
        return -1;
×
2808
    }
2809

2810
    return m_poToolDefTable->AddFontDefRef(psDef);
8✔
2811
}
2812

2813
/**********************************************************************
2814
 *                   TABMAPFile::ReadSymbolDef()
2815
 *
2816
 * Fill the TABSymbolDef structure with the definition of the specified Symbol
2817
 * index... (1-based Symbol index)
2818
 *
2819
 * If nSymbolIndex==0 or is invalid, then the structure is cleared.
2820
 *
2821
 * Returns 0 on success, -1 on error (i.e. Symbol not found).
2822
 **********************************************************************/
2823
int TABMAPFile::ReadSymbolDef(int nSymbolIndex, TABSymbolDef *psDef)
546,530✔
2824
{
2825
    if (m_poToolDefTable == nullptr && InitDrawingTools() != 0)
546,530✔
2826
        return -1;
×
2827

2828
    TABSymbolDef *psTmp = nullptr;
546,530✔
2829
    if (psDef && m_poToolDefTable &&
1,093,060✔
2830
        (psTmp = m_poToolDefTable->GetSymbolDefRef(nSymbolIndex)) != nullptr)
546,530✔
2831
    {
2832
        *psDef = *psTmp;
546,530✔
2833
    }
2834
    else if (psDef)
×
2835
    {
2836
        /* Init to MapInfo default */
2837
        static const TABSymbolDef csDefaultSymbol = MITAB_SYMBOL_DEFAULT;
2838
        *psDef = csDefaultSymbol;
×
2839
        return -1;
×
2840
    }
2841
    return 0;
546,530✔
2842
}
2843

2844
/**********************************************************************
2845
 *                   TABMAPFile::WriteSymbolDef()
2846
 *
2847
 * Write a Symbol Tool to the map file and return the Symbol index that has
2848
 * been attributed to this Symbol tool definition, or -1 if something went
2849
 * wrong
2850
 *
2851
 * Note that the returned index is a 1-based index.  A value of 0
2852
 * indicates "none" in MapInfo.
2853

2854
 * Returns a value >= 0 on success, -1 on error
2855
 **********************************************************************/
2856
int TABMAPFile::WriteSymbolDef(TABSymbolDef *psDef)
14,689✔
2857
{
2858
    if (psDef == nullptr ||
29,378✔
2859
        (m_poToolDefTable == nullptr && InitDrawingTools() != 0) ||
29,378✔
2860
        m_poToolDefTable == nullptr)
14,689✔
2861
    {
2862
        return -1;
×
2863
    }
2864

2865
    return m_poToolDefTable->AddSymbolDefRef(psDef);
14,689✔
2866
}
2867

2868
static void ORDER_MIN_MAX(double &min, double &max)
133,250✔
2869
{
2870
    if (max < min)
133,250✔
2871
        std::swap(min, max);
1✔
2872
}
133,250✔
2873

2874
static void ORDER_MIN_MAX(int &min, int &max)
133,250✔
2875
{
2876
    if (max < min)
133,250✔
2877
        std::swap(min, max);
×
2878
}
133,250✔
2879

2880
/**********************************************************************
2881
 *                   TABMAPFile::SetCoordFilter()
2882
 *
2883
 * Set the MBR of the area of interest... only objects that at least
2884
 * overlap with that area will be returned.
2885
 *
2886
 * @param sMin minimum x/y the file's projection coord.
2887
 * @param sMax maximum x/y the file's projection coord.
2888
 **********************************************************************/
2889
void TABMAPFile::SetCoordFilter(TABVertex sMin, TABVertex sMax)
31,364✔
2890
{
2891
    m_sMinFilter = sMin;
31,364✔
2892
    m_sMaxFilter = sMax;
31,364✔
2893

2894
    Coordsys2Int(sMin.x, sMin.y, m_XMinFilter, m_YMinFilter, TRUE);
31,364✔
2895
    Coordsys2Int(sMax.x, sMax.y, m_XMaxFilter, m_YMaxFilter, TRUE);
31,364✔
2896

2897
    ORDER_MIN_MAX(m_XMinFilter, m_XMaxFilter);
31,364✔
2898
    ORDER_MIN_MAX(m_YMinFilter, m_YMaxFilter);
31,364✔
2899
    ORDER_MIN_MAX(m_sMinFilter.x, m_sMaxFilter.x);
31,364✔
2900
    ORDER_MIN_MAX(m_sMinFilter.y, m_sMaxFilter.y);
31,364✔
2901
}
31,364✔
2902

2903
/**********************************************************************
2904
 *                   TABMAPFile::ResetCoordFilter()
2905
 *
2906
 * Reset the MBR of the area of interest to be the extents as defined
2907
 * in the header.
2908
 **********************************************************************/
2909

2910
void TABMAPFile::ResetCoordFilter()
35,261✔
2911

2912
{
2913
    m_XMinFilter = m_poHeader->m_nXMin;
35,261✔
2914
    m_YMinFilter = m_poHeader->m_nYMin;
35,261✔
2915
    m_XMaxFilter = m_poHeader->m_nXMax;
35,261✔
2916
    m_YMaxFilter = m_poHeader->m_nYMax;
35,261✔
2917
    Int2Coordsys(m_XMinFilter, m_YMinFilter, m_sMinFilter.x, m_sMinFilter.y);
35,261✔
2918
    Int2Coordsys(m_XMaxFilter, m_YMaxFilter, m_sMaxFilter.x, m_sMaxFilter.y);
35,261✔
2919

2920
    ORDER_MIN_MAX(m_XMinFilter, m_XMaxFilter);
35,261✔
2921
    ORDER_MIN_MAX(m_YMinFilter, m_YMaxFilter);
35,261✔
2922
    ORDER_MIN_MAX(m_sMinFilter.x, m_sMaxFilter.x);
35,261✔
2923
    ORDER_MIN_MAX(m_sMinFilter.y, m_sMaxFilter.y);
35,261✔
2924
}
35,261✔
2925

2926
/**********************************************************************
2927
 *                   TABMAPFile::GetCoordFilter()
2928
 *
2929
 * Get the MBR of the area of interest, as previously set by
2930
 * SetCoordFilter().
2931
 *
2932
 * @param sMin vertex into which the minimum x/y values put in coordsys space.
2933
 * @param sMax vertex into which the maximum x/y values put in coordsys space.
2934
 **********************************************************************/
2935
void TABMAPFile::GetCoordFilter(TABVertex &sMin, TABVertex &sMax) const
31,399✔
2936
{
2937
    sMin = m_sMinFilter;
31,399✔
2938
    sMax = m_sMaxFilter;
31,399✔
2939
}
31,399✔
2940

2941
/**********************************************************************
2942
 *                   TABMAPFile::CommitSpatialIndex()
2943
 *
2944
 * Write the spatial index blocks tree for this file.
2945
 *
2946
 * This function applies only to write access mode.
2947
 *
2948
 * Returns 0 on success, -1 on error.
2949
 **********************************************************************/
2950
int TABMAPFile::CommitSpatialIndex()
1,201✔
2951
{
2952
    if (m_eAccessMode == TABRead || m_poHeader == nullptr)
1,201✔
2953
    {
2954
        CPLError(
×
2955
            CE_Failure, CPLE_AssertionFailed,
2956
            "CommitSpatialIndex() failed: file not opened for write access.");
2957
        return -1;
×
2958
    }
2959

2960
    if (m_poSpIndex == nullptr)
1,201✔
2961
    {
2962
        return 0;  // Nothing to do!
36✔
2963
    }
2964

2965
    /*-------------------------------------------------------------
2966
     * Update header fields and commit index block
2967
     * (its children will be recursively committed as well)
2968
     *------------------------------------------------------------*/
2969
    // Add 1 to Spatial Index Depth to account to the MapObjectBlocks
2970
    const int nNextDepth = m_poSpIndex->GetCurMaxDepth() + 1;
1,165✔
2971
    m_poHeader->m_nMaxSpIndexDepth = static_cast<GByte>(
1,165✔
2972
        std::max(static_cast<int>(m_poHeader->m_nMaxSpIndexDepth), nNextDepth));
1,165✔
2973

2974
    m_poSpIndex->GetMBR(m_poHeader->m_nXMin, m_poHeader->m_nYMin,
1,165✔
2975
                        m_poHeader->m_nXMax, m_poHeader->m_nYMax);
1,165✔
2976

2977
    return m_poSpIndex->CommitToFile();
1,165✔
2978
}
2979

2980
/**********************************************************************
2981
 *                   TABMAPFile::GetMinTABFileVersion()
2982
 *
2983
 * Returns the minimum TAB file version number that can contain all the
2984
 * objects stored in this file.
2985
 **********************************************************************/
2986
int TABMAPFile::GetMinTABFileVersion()
144✔
2987
{
2988
    int nToolVersion = 0;
144✔
2989

2990
    if (m_poToolDefTable)
144✔
2991
        nToolVersion = m_poToolDefTable->GetMinVersionNumber();
108✔
2992

2993
    return std::max(nToolVersion, m_nMinTABVersion);
144✔
2994
}
2995

2996
const CPLString &TABMAPFile::GetEncoding() const
13✔
2997
{
2998
    return m_osEncoding;
13✔
2999
}
3000

3001
void TABMAPFile::SetEncoding(const CPLString &osEncoding)
2✔
3002
{
3003
    m_osEncoding = osEncoding;
2✔
3004
}
2✔
3005

3006
bool TABMAPFile::IsValidObjType(int nObjType)
1,133,070✔
3007
{
3008
    switch (nObjType)
1,133,070✔
3009
    {
3010
        case TAB_GEOM_NONE:
1,133,070✔
3011
        case TAB_GEOM_SYMBOL_C:
3012
        case TAB_GEOM_SYMBOL:
3013
        case TAB_GEOM_LINE_C:
3014
        case TAB_GEOM_LINE:
3015
        case TAB_GEOM_PLINE_C:
3016
        case TAB_GEOM_PLINE:
3017
        case TAB_GEOM_ARC_C:
3018
        case TAB_GEOM_ARC:
3019
        case TAB_GEOM_REGION_C:
3020
        case TAB_GEOM_REGION:
3021
        case TAB_GEOM_TEXT_C:
3022
        case TAB_GEOM_TEXT:
3023
        case TAB_GEOM_RECT_C:
3024
        case TAB_GEOM_RECT:
3025
        case TAB_GEOM_ROUNDRECT_C:
3026
        case TAB_GEOM_ROUNDRECT:
3027
        case TAB_GEOM_ELLIPSE_C:
3028
        case TAB_GEOM_ELLIPSE:
3029
        case TAB_GEOM_MULTIPLINE_C:
3030
        case TAB_GEOM_MULTIPLINE:
3031
        case TAB_GEOM_FONTSYMBOL_C:
3032
        case TAB_GEOM_FONTSYMBOL:
3033
        case TAB_GEOM_CUSTOMSYMBOL_C:
3034
        case TAB_GEOM_CUSTOMSYMBOL:
3035
        case TAB_GEOM_V450_REGION_C:
3036
        case TAB_GEOM_V450_REGION:
3037
        case TAB_GEOM_V450_MULTIPLINE_C:
3038
        case TAB_GEOM_V450_MULTIPLINE:
3039
        case TAB_GEOM_MULTIPOINT_C:
3040
        case TAB_GEOM_MULTIPOINT:
3041
        case TAB_GEOM_COLLECTION_C:
3042
        case TAB_GEOM_COLLECTION:
3043
        case TAB_GEOM_UNKNOWN1_C:
3044
        case TAB_GEOM_UNKNOWN1:
3045
        case TAB_GEOM_V800_REGION_C:
3046
        case TAB_GEOM_V800_REGION:
3047
        case TAB_GEOM_V800_MULTIPLINE_C:
3048
        case TAB_GEOM_V800_MULTIPLINE:
3049
        case TAB_GEOM_V800_MULTIPOINT_C:
3050
        case TAB_GEOM_V800_MULTIPOINT:
3051
        case TAB_GEOM_V800_COLLECTION_C:
3052
        case TAB_GEOM_V800_COLLECTION:
3053
            return true;
1,133,070✔
3054

3055
        default:
×
3056
            return false;
×
3057
    }
3058
}
3059

3060
/**********************************************************************
3061
 *                   TABMAPFile::Dump()
3062
 *
3063
 * Dump block contents... available only in DEBUG mode.
3064
 **********************************************************************/
3065
#ifdef DEBUG
3066

3067
void TABMAPFile::Dump(FILE *fpOut /*=NULL*/)
×
3068
{
3069
    if (fpOut == nullptr)
×
3070
        fpOut = stdout;
×
3071

3072
    fprintf(fpOut, "----- TABMAPFile::Dump() -----\n");
×
3073

3074
    if (m_fp == nullptr)
×
3075
    {
3076
        fprintf(fpOut, "File is not opened.\n");
×
3077
    }
3078
    else
3079
    {
3080
        fprintf(fpOut, "File is opened: %s\n", m_pszFname);
×
3081
        fprintf(fpOut, "Coordsys filter  = (%g,%g)-(%g,%g)\n", m_sMinFilter.x,
×
3082
                m_sMinFilter.y, m_sMaxFilter.x, m_sMaxFilter.y);
3083
        fprintf(fpOut, "Int coord filter = (%d,%d)-(%d,%d)\n", m_XMinFilter,
×
3084
                m_YMinFilter, m_XMaxFilter, m_YMaxFilter);
3085

3086
        fprintf(fpOut, "\nFile Header follows ...\n\n");
×
3087
        m_poHeader->Dump(fpOut);
×
3088
        fprintf(fpOut, "... end of file header.\n\n");
×
3089

3090
        fprintf(fpOut, "Associated .ID file ...\n\n");
×
3091
        m_poIdIndex->Dump(fpOut);
×
3092
        fprintf(fpOut, "... end of ID file dump.\n\n");
×
3093
    }
3094

3095
    fflush(fpOut);
×
3096
}
×
3097

3098
#endif  // DEBUG
3099

3100
/**********************************************************************
3101
 *                   TABMAPFile::DumpSpatialIndexToMIF()
3102
 *
3103
 * Dump the spatial index tree... available only in DEBUG mode.
3104
 **********************************************************************/
3105
#ifdef DEBUG
3106

3107
void TABMAPFile::DumpSpatialIndexToMIF(TABMAPIndexBlock *poNode, FILE *fpMIF,
×
3108
                                       FILE *fpMID, int nParentId /*=-1*/,
3109
                                       int nIndexInNode /*=-1*/,
3110
                                       int nCurDepth /*=0*/,
3111
                                       int nMaxDepth /*=-1*/)
3112
{
3113
    if (poNode == nullptr)
×
3114
    {
3115
        if (m_poHeader && m_poHeader->m_nFirstIndexBlock != 0)
×
3116
        {
3117
            TABRawBinBlock *poBlock =
3118
                GetIndexObjectBlock(m_poHeader->m_nFirstIndexBlock);
×
3119
            if (poBlock && poBlock->GetBlockType() == TABMAP_INDEX_BLOCK)
×
3120
                poNode = cpl::down_cast<TABMAPIndexBlock *>(poBlock);
×
3121
        }
3122

3123
        if (poNode == nullptr)
×
3124
            return;
×
3125
    }
3126

3127
    /*-------------------------------------------------------------
3128
     * Report info on current tree node
3129
     *------------------------------------------------------------*/
3130
    const int numEntries = poNode->GetNumEntries();
×
3131
    GInt32 nXMin = 0;
×
3132
    GInt32 nYMin = 0;
×
3133
    GInt32 nXMax = 0;
×
3134
    GInt32 nYMax = 0;
×
3135

3136
    poNode->RecomputeMBR();
×
3137
    poNode->GetMBR(nXMin, nYMin, nXMax, nYMax);
×
3138

3139
    double dXMin = 0.0;
×
3140
    double dYMin = 0.0;
×
3141
    double dXMax = 0.0;
×
3142
    double dYMax = 0.0;
×
3143
    Int2Coordsys(nXMin, nYMin, dXMin, dYMin);
×
3144
    Int2Coordsys(nXMax, nYMax, dXMax, dYMax);
×
3145

3146
    VSIFPrintf(fpMIF, "RECT %g %g %g %g\n", dXMin, dYMin, dXMax, dYMax);
×
3147
    VSIFPrintf(fpMIF, "  Brush(1, 0)\n"); /* No fill */
×
3148

3149
    VSIFPrintf(fpMID, "%d,%d,%d,%d,%g,%d,%d,%d,%d\n", poNode->GetStartAddress(),
×
3150
               nParentId, nIndexInNode, nCurDepth,
3151
               MITAB_AREA(nXMin, nYMin, nXMax, nYMax), nXMin, nYMin, nXMax,
×
3152
               nYMax);
3153

3154
    if (nMaxDepth != 0)
×
3155
    {
3156
        /*-------------------------------------------------------------
3157
         * Loop through all entries, dumping each of them
3158
         *------------------------------------------------------------*/
3159
        for (int i = 0; i < numEntries; i++)
×
3160
        {
3161
            TABMAPIndexEntry *psEntry = poNode->GetEntry(i);
×
3162

3163
            TABRawBinBlock *poBlock = GetIndexObjectBlock(psEntry->nBlockPtr);
×
3164
            if (poBlock == nullptr)
×
3165
                continue;
×
3166

3167
            if (poBlock->GetBlockType() == TABMAP_INDEX_BLOCK)
×
3168
            {
3169
                /* Index block, dump recursively */
3170
                DumpSpatialIndexToMIF(
×
3171
                    cpl::down_cast<TABMAPIndexBlock *>(poBlock), fpMIF, fpMID,
3172
                    poNode->GetStartAddress(), i, nCurDepth + 1, nMaxDepth - 1);
3173
            }
3174
            else
3175
            {
3176
                /* Object block, dump directly */
3177
                CPLAssert(poBlock->GetBlockType() == TABMAP_OBJECT_BLOCK);
×
3178

3179
                Int2Coordsys(psEntry->XMin, psEntry->YMin, dXMin, dYMin);
×
3180
                Int2Coordsys(psEntry->XMax, psEntry->YMax, dXMax, dYMax);
×
3181

3182
                VSIFPrintf(fpMIF, "RECT %g %g %g %g\n", dXMin, dYMin, dXMax,
×
3183
                           dYMax);
3184
                VSIFPrintf(fpMIF, "  Brush(1, 0)\n"); /* No fill */
×
3185

3186
                VSIFPrintf(
×
3187
                    fpMID, "%d,%d,%d,%d,%g,%d,%d,%d,%d\n", psEntry->nBlockPtr,
3188
                    poNode->GetStartAddress(), i, nCurDepth + 1,
3189
                    MITAB_AREA(psEntry->XMin, psEntry->YMin, psEntry->XMax,
×
3190
                               psEntry->YMax),
3191
                    psEntry->XMin, psEntry->YMin, psEntry->XMax, psEntry->YMax);
3192
            }
3193

3194
            delete poBlock;
×
3195
        }
3196
    }
3197
}
3198

3199
#endif  // DEBUG
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc