Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/mitab/mitab_mapfile.cpp
Line
Count
Source
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
#include <algorithm>
23
#include <utility>
24
25
#include "cpl_conv.h"
26
#include "cpl_error.h"
27
#include "cpl_vsi.h"
28
#include "mitab_priv.h"
29
#include "ogr_feature.h"
30
31
/*=====================================================================
32
 *                      class TABMAPFile
33
 *====================================================================*/
34
35
/**********************************************************************
36
 *                   TABMAPFile::TABMAPFile()
37
 *
38
 * Constructor.
39
 **********************************************************************/
40
TABMAPFile::TABMAPFile(const char *pszEncoding)
41
2.18k
    : m_nMinTABVersion(300), m_pszFname(nullptr), m_fp(nullptr),
42
2.18k
      m_eAccessMode(TABRead), m_poHeader(nullptr), m_poSpIndex(nullptr),
43
      // See bug 1732: Optimized spatial index produces broken files because of
44
      // the way CoordBlocks are split. For now we have to force using the quick
45
      // (old) spatial index mode by default until bug 1732 is fixed.
46
2.18k
      m_bQuickSpatialIndexMode(TRUE), m_poIdIndex(nullptr),
47
2.18k
      m_poCurObjBlock(nullptr), m_nCurObjPtr(-1), m_nCurObjType(TAB_GEOM_UNSET),
48
2.18k
      m_nCurObjId(-1), m_poCurCoordBlock(nullptr), m_poToolDefTable(nullptr),
49
2.18k
      m_XMinFilter(0), m_YMinFilter(0), m_XMaxFilter(0), m_YMaxFilter(0),
50
2.18k
      m_bUpdated(FALSE), m_bLastOpWasRead(FALSE), m_bLastOpWasWrite(FALSE),
51
2.18k
      m_poSpIndexLeaf(nullptr), m_osEncoding(pszEncoding)
52
2.18k
{
53
2.18k
    m_sMinFilter.x = 0;
54
2.18k
    m_sMinFilter.y = 0;
55
2.18k
    m_sMaxFilter.x = 0;
56
2.18k
    m_sMaxFilter.y = 0;
57
58
2.18k
    m_oBlockManager.SetName("MAP");
59
2.18k
}
60
61
/**********************************************************************
62
 *                   TABMAPFile::~TABMAPFile()
63
 *
64
 * Destructor.
65
 **********************************************************************/
66
TABMAPFile::~TABMAPFile()
67
2.18k
{
68
2.18k
    Close();
69
2.18k
}
70
71
/**********************************************************************
72
 *                   TABMAPFile::Open()
73
 *
74
 * Compatibility layer with new interface.
75
 * Return 0 on success, -1 in case of failure.
76
 **********************************************************************/
77
78
int TABMAPFile::Open(const char *pszFname, const char *pszAccess,
79
                     GBool bNoErrorMsg, int nBlockSizeForCreate)
80
0
{
81
    // cppcheck-suppress nullPointer
82
0
    if (STARTS_WITH_CI(pszAccess, "r"))
83
0
        return Open(pszFname, TABRead, bNoErrorMsg, nBlockSizeForCreate);
84
0
    else if (STARTS_WITH_CI(pszAccess, "w"))
85
0
        return Open(pszFname, TABWrite, bNoErrorMsg, nBlockSizeForCreate);
86
0
    else
87
0
    {
88
0
        CPLError(CE_Failure, CPLE_FileIO,
89
0
                 "Open() failed: access mode \"%s\" not supported", pszAccess);
90
0
        return -1;
91
0
    }
92
0
}
93
94
/**********************************************************************
95
 *                   TABMAPFile::Open()
96
 *
97
 * Open a .MAP file, and initialize the structures to be ready to read
98
 * objects from it.
99
 *
100
 * Since .MAP and .ID files are optional, you can set bNoErrorMsg=TRUE to
101
 * disable the error message and receive an return value of 1 if file
102
 * cannot be opened.
103
 * In this case, only the methods MoveToObjId() and GetCurObjType() can
104
 * be used.  They will behave as if the .ID file contained only null
105
 * references, so all object will look like they have NONE geometries.
106
 *
107
 * Returns 0 on success, 1 when the .map file does not exist, -1 on error.
108
 **********************************************************************/
109
int TABMAPFile::Open(const char *pszFname, TABAccess eAccess,
110
                     GBool bNoErrorMsg /* = FALSE */,
111
                     int nBlockSizeForCreate /* = 512 */)
112
2.18k
{
113
2.18k
    CPLErrorReset();
114
115
2.18k
    VSILFILE *fp = nullptr;
116
2.18k
    TABRawBinBlock *poBlock = nullptr;
117
118
2.18k
    if (m_fp)
119
0
    {
120
0
        CPLError(CE_Failure, CPLE_FileIO,
121
0
                 "Open() failed: object already contains an open file");
122
0
        return -1;
123
0
    }
124
125
2.18k
    m_nMinTABVersion = 300;
126
2.18k
    m_fp = nullptr;
127
2.18k
    m_poHeader = nullptr;
128
2.18k
    m_poIdIndex = nullptr;
129
2.18k
    m_poSpIndex = nullptr;
130
2.18k
    m_poToolDefTable = nullptr;
131
2.18k
    m_eAccessMode = eAccess;
132
2.18k
    m_bUpdated = FALSE;
133
2.18k
    m_bLastOpWasRead = FALSE;
134
2.18k
    m_bLastOpWasWrite = FALSE;
135
136
2.18k
    if (m_eAccessMode == TABWrite &&
137
231
        (nBlockSizeForCreate < TAB_MIN_BLOCK_SIZE ||
138
231
         nBlockSizeForCreate > TAB_MAX_BLOCK_SIZE ||
139
231
         (nBlockSizeForCreate % TAB_MIN_BLOCK_SIZE) != 0))
140
0
    {
141
0
        CPLError(CE_Failure, CPLE_NotSupported,
142
0
                 "Open() failed: invalid block size: %d", nBlockSizeForCreate);
143
0
        return -1;
144
0
    }
145
146
    /*-----------------------------------------------------------------
147
     * Open file
148
     *----------------------------------------------------------------*/
149
2.18k
    const char *pszAccess = (eAccess == TABRead)    ? "rb"
150
2.18k
                            : (eAccess == TABWrite) ? "wb+"
151
231
                                                    : "rb+";
152
2.18k
    fp = VSIFOpenL(pszFname, pszAccess);
153
154
2.18k
    m_oBlockManager.Reset();
155
156
2.18k
    if (fp != nullptr &&
157
1.53k
        (m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite))
158
1.30k
    {
159
        /*-----------------------------------------------------------------
160
         * Read access: try to read header block
161
         * First try with a 512 bytes block to check the .map version.
162
         * If it is version 500 or more then read again a 1024 bytes block
163
         *----------------------------------------------------------------*/
164
1.30k
        poBlock = TABCreateMAPBlockFromFile(fp, 0, 512, TRUE, m_eAccessMode);
165
166
1.30k
        if (poBlock && poBlock->GetBlockClass() == TABMAP_HEADER_BLOCK &&
167
1.24k
            cpl::down_cast<TABMAPHeaderBlock *>(poBlock)->m_nMAPVersionNumber >=
168
1.24k
                500)
169
1.24k
        {
170
            // Version 500 or higher.  Read 1024 bytes block instead of 512
171
1.24k
            delete poBlock;
172
1.24k
            poBlock =
173
1.24k
                TABCreateMAPBlockFromFile(fp, 0, 1024, TRUE, m_eAccessMode);
174
1.24k
        }
175
176
1.30k
        if (poBlock == nullptr ||
177
1.23k
            poBlock->GetBlockClass() != TABMAP_HEADER_BLOCK)
178
70
        {
179
70
            if (poBlock)
180
0
                delete poBlock;
181
70
            poBlock = nullptr;
182
70
            VSIFCloseL(fp);
183
70
            CPLError(
184
70
                CE_Failure, CPLE_FileIO,
185
70
                "Open() failed: %s does not appear to be a valid .MAP file",
186
70
                pszFname);
187
70
            return -1;
188
70
        }
189
1.23k
        m_oBlockManager.SetBlockSize(
190
1.23k
            cpl::down_cast<TABMAPHeaderBlock *>(poBlock)->m_nRegularBlockSize);
191
1.23k
    }
192
877
    else if (fp != nullptr && m_eAccessMode == TABWrite)
193
231
    {
194
        /*-----------------------------------------------------------------
195
         * Write access: create a new header block
196
         * .MAP files of Version 500 and up appear to have a 1024 bytes
197
         * header.  The last 512 bytes are usually all zeros.
198
         *----------------------------------------------------------------*/
199
231
        m_poHeader = new TABMAPHeaderBlock(m_eAccessMode);
200
231
        poBlock = m_poHeader;
201
231
        poBlock->InitNewBlock(fp, nBlockSizeForCreate, 0);
202
203
231
        m_oBlockManager.SetBlockSize(m_poHeader->m_nRegularBlockSize);
204
231
        if (m_poHeader->m_nRegularBlockSize == 512)
205
231
            m_oBlockManager.SetLastPtr(512);
206
0
        else
207
0
            m_oBlockManager.SetLastPtr(0);
208
209
231
        m_bUpdated = TRUE;
210
231
    }
211
646
    else if (bNoErrorMsg)
212
646
    {
213
        /*-----------------------------------------------------------------
214
         * .MAP does not exist... produce no error message, but set
215
         * the class members so that MoveToObjId() and GetCurObjType()
216
         * can be used to return only NONE geometries.
217
         *----------------------------------------------------------------*/
218
646
        m_fp = nullptr;
219
646
        m_nCurObjType = TAB_GEOM_NONE;
220
221
        /* Create a false header block that will return default
222
         * values for projection and coordsys conversion stuff...
223
         */
224
646
        m_poHeader = new TABMAPHeaderBlock(m_eAccessMode);
225
646
        m_poHeader->InitNewBlock(nullptr, 512, 0);
226
227
646
        return 1;
228
646
    }
229
0
    else
230
0
    {
231
0
        CPLError(CE_Failure, CPLE_FileIO, "Open() failed for %s", pszFname);
232
0
        return -1;
233
0
    }
234
235
    /*-----------------------------------------------------------------
236
     * File appears to be valid... set the various class members
237
     *----------------------------------------------------------------*/
238
1.46k
    m_fp = fp;
239
1.46k
    m_poHeader = cpl::down_cast<TABMAPHeaderBlock *>(poBlock);
240
1.46k
    m_pszFname = CPLStrdup(pszFname);
241
242
    /*-----------------------------------------------------------------
243
     * Create a TABMAPObjectBlock, in READ mode only or in UPDATE mode
244
     * if there's an object
245
     *
246
     * In WRITE mode, the object block will be created only when needed.
247
     * We do not create the object block in the open() call because
248
     * files that contained only "NONE" geometries ended up with empty
249
     * object and spatial index blocks.
250
     *----------------------------------------------------------------*/
251
252
1.46k
    if (m_eAccessMode == TABRead ||
253
231
        (m_eAccessMode == TABReadWrite && m_poHeader->m_nFirstIndexBlock != 0))
254
1.23k
    {
255
1.23k
        m_poCurObjBlock = new TABMAPObjectBlock(m_eAccessMode);
256
1.23k
        m_poCurObjBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize);
257
1.23k
    }
258
231
    else
259
231
    {
260
231
        m_poCurObjBlock = nullptr;
261
231
    }
262
263
    /*-----------------------------------------------------------------
264
     * Open associated .ID (object id index) file
265
     *----------------------------------------------------------------*/
266
1.46k
    m_poIdIndex = new TABIDFile;
267
1.46k
    if (m_poIdIndex->Open(pszFname, m_eAccessMode) != 0)
268
8
    {
269
        // Failed... an error has already been reported
270
8
        Close();
271
8
        return -1;
272
8
    }
273
274
    /*-----------------------------------------------------------------
275
     * Default Coord filter is the MBR of the whole file
276
     * This is currently unused but could eventually be used to handle
277
     * spatial filters more efficiently.
278
     *----------------------------------------------------------------*/
279
1.45k
    if (m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite)
280
1.22k
    {
281
1.22k
        ResetCoordFilter();
282
1.22k
    }
283
284
    /*-----------------------------------------------------------------
285
     * We could scan a file through its quad tree index... but we don't!
286
     *
287
     * In read mode, we just ignore the spatial index.
288
     *
289
     * In write mode the index is created and maintained as new object
290
     * blocks are added inside CommitObjBlock().
291
     *----------------------------------------------------------------*/
292
1.45k
    m_poSpIndex = nullptr;
293
294
1.45k
    if (m_eAccessMode == TABReadWrite)
295
0
    {
296
        /* We don't allow quick mode in read/write mode */
297
0
        m_bQuickSpatialIndexMode = FALSE;
298
299
0
        if (m_poHeader->m_nFirstIndexBlock != 0)
300
0
        {
301
0
            poBlock = GetIndexObjectBlock(m_poHeader->m_nFirstIndexBlock);
302
0
            if (poBlock == nullptr ||
303
0
                (poBlock->GetBlockType() != TABMAP_INDEX_BLOCK &&
304
0
                 poBlock->GetBlockType() != TABMAP_OBJECT_BLOCK))
305
0
            {
306
0
                CPLError(CE_Failure, CPLE_AppDefined,
307
0
                         "Cannot find first index block at offset %d",
308
0
                         m_poHeader->m_nFirstIndexBlock);
309
0
                delete poBlock;
310
0
            }
311
0
            else if (poBlock->GetBlockType() == TABMAP_INDEX_BLOCK)
312
0
            {
313
0
                m_poSpIndex = cpl::down_cast<TABMAPIndexBlock *>(poBlock);
314
0
                m_poSpIndex->SetMBR(m_poHeader->m_nXMin, m_poHeader->m_nYMin,
315
0
                                    m_poHeader->m_nXMax, m_poHeader->m_nYMax);
316
0
            }
317
0
            else /* if( poBlock->GetBlockType() == TABMAP_OBJECT_BLOCK ) */
318
0
            {
319
                /* This can happen if the file created by MapInfo contains just
320
                 */
321
                /* a few objects */
322
0
                delete poBlock;
323
0
            }
324
0
        }
325
0
    }
326
327
    /*-----------------------------------------------------------------
328
     * Initialization of the Drawing Tools table will be done automatically
329
     * as Read/Write calls are done later.
330
     *----------------------------------------------------------------*/
331
1.45k
    m_poToolDefTable = nullptr;
332
333
1.45k
    if (m_eAccessMode == TABReadWrite)
334
0
    {
335
0
        InitDrawingTools();
336
0
    }
337
338
1.45k
    if (m_eAccessMode == TABReadWrite)
339
0
    {
340
0
        VSIStatBufL sStatBuf;
341
0
        if (VSIStatL(m_pszFname, &sStatBuf) != 0)
342
0
        {
343
0
            Close();
344
0
            return -1;
345
0
        }
346
0
        m_oBlockManager.SetLastPtr(static_cast<int>(
347
0
            ((sStatBuf.st_size - 1) / m_poHeader->m_nRegularBlockSize) *
348
0
            m_poHeader->m_nRegularBlockSize));
349
350
        /* Read chain of garbage blocks */
351
0
        if (m_poHeader->m_nFirstGarbageBlock != 0)
352
0
        {
353
0
            int nCurGarbBlock = m_poHeader->m_nFirstGarbageBlock;
354
0
            m_oBlockManager.PushGarbageBlockAsLast(nCurGarbBlock);
355
0
            while (true)
356
0
            {
357
0
                GUInt16 nBlockType = 0;
358
0
                int nNextGarbBlockPtr = 0;
359
0
                if (VSIFSeekL(fp, static_cast<vsi_l_offset>(nCurGarbBlock),
360
0
                              SEEK_SET) != 0 ||
361
0
                    VSIFReadL(&nBlockType, sizeof(nBlockType), 1, fp) != 1 ||
362
0
                    VSIFReadL(&nNextGarbBlockPtr, sizeof(nNextGarbBlockPtr), 1,
363
0
                              fp) != 1)
364
0
                {
365
0
                    CPLError(CE_Failure, CPLE_AppDefined,
366
0
                             "Cannot read garbage block at offset %d",
367
0
                             nCurGarbBlock);
368
0
                    break;
369
0
                }
370
0
                if (nBlockType != TABMAP_GARB_BLOCK)
371
0
                {
372
0
                    CPLError(CE_Failure, CPLE_AppDefined,
373
0
                             "Got block type (%d) instead of %d at offset %d",
374
0
                             nBlockType, TABMAP_GARB_BLOCK, nCurGarbBlock);
375
0
                }
376
0
                if (nNextGarbBlockPtr == 0)
377
0
                    break;
378
0
                nCurGarbBlock = nNextGarbBlockPtr;
379
0
                m_oBlockManager.PushGarbageBlockAsLast(nCurGarbBlock);
380
0
            }
381
0
        }
382
0
    }
383
384
    /*-----------------------------------------------------------------
385
     * Make sure all previous calls succeeded.
386
     *----------------------------------------------------------------*/
387
1.45k
    if (CPLGetLastErrorType() == CE_Failure)
388
0
    {
389
        // Open Failed... an error has already been reported
390
0
        Close();
391
0
        return -1;
392
0
    }
393
394
1.45k
    return 0;
395
1.45k
}
396
397
/**********************************************************************
398
 *                   TABMAPFile::Close()
399
 *
400
 * Close current file, and release all memory used.
401
 *
402
 * Returns 0 on success, -1 on error.
403
 **********************************************************************/
404
int TABMAPFile::Close()
405
4.37k
{
406
    // Check if file is opened... it is possible to have a fake header
407
    // without an actual file attached to it.
408
4.37k
    if (m_fp == nullptr && m_poHeader == nullptr)
409
2.26k
        return 0;
410
411
    /*----------------------------------------------------------------
412
     * Write access: commit latest changes to the file.
413
     *---------------------------------------------------------------*/
414
2.11k
    if (m_eAccessMode != TABRead)
415
231
    {
416
231
        SyncToDisk();
417
231
    }
418
419
    // Delete all structures
420
2.11k
    if (m_poHeader)
421
2.11k
        delete m_poHeader;
422
2.11k
    m_poHeader = nullptr;
423
424
2.11k
    if (m_poIdIndex)
425
1.46k
    {
426
1.46k
        m_poIdIndex->Close();
427
1.46k
        delete m_poIdIndex;
428
1.46k
        m_poIdIndex = nullptr;
429
1.46k
    }
430
431
2.11k
    if (m_poCurObjBlock)
432
1.39k
    {
433
1.39k
        delete m_poCurObjBlock;
434
1.39k
        m_poCurObjBlock = nullptr;
435
1.39k
        m_nCurObjPtr = -1;
436
1.39k
        m_nCurObjType = TAB_GEOM_UNSET;
437
1.39k
        m_nCurObjId = -1;
438
1.39k
    }
439
440
2.11k
    if (m_poCurCoordBlock)
441
201
    {
442
201
        delete m_poCurCoordBlock;
443
201
        m_poCurCoordBlock = nullptr;
444
201
    }
445
446
2.11k
    if (m_poSpIndex)
447
164
    {
448
164
        delete m_poSpIndex;
449
164
        m_poSpIndex = nullptr;
450
164
        m_poSpIndexLeaf = nullptr;
451
164
    }
452
453
2.11k
    if (m_poToolDefTable)
454
244
    {
455
244
        delete m_poToolDefTable;
456
244
        m_poToolDefTable = nullptr;
457
244
    }
458
459
    // Close file
460
2.11k
    if (m_fp)
461
1.46k
        VSIFCloseL(m_fp);
462
2.11k
    m_fp = nullptr;
463
464
2.11k
    CPLFree(m_pszFname);
465
2.11k
    m_pszFname = nullptr;
466
467
2.11k
    return 0;
468
4.37k
}
469
470
/************************************************************************/
471
/*                            GetFileSize()                             */
472
/************************************************************************/
473
474
GUInt32 TABMAPFile::GetFileSize()
475
0
{
476
0
    if (!m_fp)
477
0
        return 0;
478
0
    vsi_l_offset nCurPos = VSIFTellL(m_fp);
479
0
    VSIFSeekL(m_fp, 0, SEEK_END);
480
0
    vsi_l_offset nSize = VSIFTellL(m_fp);
481
0
    VSIFSeekL(m_fp, nCurPos, SEEK_SET);
482
0
    return nSize > UINT_MAX ? UINT_MAX : static_cast<GUInt32>(nSize);
483
0
}
484
485
/************************************************************************/
486
/*                             SyncToDisk()                             */
487
/************************************************************************/
488
489
int TABMAPFile::SyncToDisk()
490
457
{
491
457
    if (m_eAccessMode == TABRead)
492
0
    {
493
0
        CPLError(CE_Failure, CPLE_NotSupported,
494
0
                 "SyncToDisk() can be used only with Write access.");
495
0
        return -1;
496
0
    }
497
498
457
    if (!m_bUpdated)
499
226
        return 0;
500
501
    // Start by committing current object and coord blocks
502
    // Nothing happens if none has been created yet.
503
231
    if (CommitObjAndCoordBlocks(FALSE) != 0)
504
0
        return -1;
505
506
    // Write the drawing tools definitions now.
507
231
    if (CommitDrawingTools() != 0)
508
0
        return -1;
509
510
    // Commit spatial index blocks
511
231
    if (CommitSpatialIndex() != 0)
512
0
        return -1;
513
514
    // Update header fields and commit
515
231
    if (m_poHeader)
516
231
    {
517
        // OK, with V450 files, objects are not limited to 32k nodes
518
        // any more, and this means that m_nMaxCoordBufSize can become
519
        // huge, and actually more huge than can be held in memory.
520
        // MapInfo counts m_nMaxCoordBufSize=0 for V450 objects, but
521
        // until this is cleanly implemented, we will just prevent
522
        // m_nMaxCoordBufSizefrom going beyond 512k in V450 files.
523
231
        if (m_nMinTABVersion >= 450)
524
0
        {
525
0
            m_poHeader->m_nMaxCoordBufSize =
526
0
                std::min(m_poHeader->m_nMaxCoordBufSize, 512 * 1024);
527
0
        }
528
529
        // Write Ref to beginning of the chain of garbage blocks
530
231
        m_poHeader->m_nFirstGarbageBlock =
531
231
            m_oBlockManager.GetFirstGarbageBlock();
532
533
231
        if (m_poHeader->CommitToFile() != 0)
534
0
            return -1;
535
231
    }
536
537
    // Check for overflow of internal coordinates and produce a warning
538
    // if that happened...
539
231
    if (m_poHeader && m_poHeader->m_bIntBoundsOverflow)
540
148
    {
541
148
        double dBoundsMinX = 0.0;
542
148
        double dBoundsMinY = 0.0;
543
148
        double dBoundsMaxX = 0.0;
544
148
        double dBoundsMaxY = 0.0;
545
148
        Int2Coordsys(-1000000000, -1000000000, dBoundsMinX, dBoundsMinY);
546
148
        Int2Coordsys(1000000000, 1000000000, dBoundsMaxX, dBoundsMaxY);
547
548
148
        CPLError(CE_Warning,
549
148
                 static_cast<CPLErrorNum>(TAB_WarningBoundsOverflow),
550
148
                 "Some objects were written outside of the file's "
551
148
                 "predefined bounds.\n"
552
148
                 "These objects may have invalid coordinates when the file "
553
148
                 "is reopened.\n"
554
148
                 "Predefined bounds: (%.15g,%.15g)-(%.15g,%.15g)\n",
555
148
                 dBoundsMinX, dBoundsMinY, dBoundsMaxX, dBoundsMaxY);
556
148
    }
557
558
231
    if (m_poIdIndex != nullptr && m_poIdIndex->SyncToDisk() != 0)
559
0
        return -1;
560
561
231
    m_bUpdated = FALSE;
562
563
231
    return 0;
564
231
}
565
566
/**********************************************************************
567
 *                   TABMAPFile::ReOpenReadWrite()
568
 **********************************************************************/
569
int TABMAPFile::ReOpenReadWrite()
570
0
{
571
0
    char *pszFname = m_pszFname;
572
0
    m_pszFname = nullptr;
573
0
    Close();
574
0
    if (Open(pszFname, TABReadWrite) < 0)
575
0
    {
576
0
        CPLFree(pszFname);
577
0
        return -1;
578
0
    }
579
0
    CPLFree(pszFname);
580
0
    return 0;
581
0
}
582
583
/**********************************************************************
584
 *                   TABMAPFile::SetQuickSpatialIndexMode()
585
 *
586
 * Select "quick spatial index mode".
587
 *
588
 * The default behavior of MITAB is to generate an optimized spatial index,
589
 * but this results in slower write speed.
590
 *
591
 * Applications that want faster write speed and do not care
592
 * about the performance of spatial queries on the resulting file can
593
 * use SetQuickSpatialIndexMode() to require the creation of a non-optimal
594
 * spatial index (actually emulating the type of spatial index produced
595
 * by MITAB before version 1.6.0). In this mode writing files can be
596
 * about 5 times faster, but spatial queries can be up to 30 times slower.
597
 *
598
 * Returns 0 on success, -1 on error.
599
 **********************************************************************/
600
int TABMAPFile::SetQuickSpatialIndexMode(GBool bQuickSpatialIndexMode /*=TRUE*/)
601
0
{
602
0
    if (m_eAccessMode != TABWrite)
603
0
    {
604
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
605
0
                 "SetQuickSpatialIndexMode() failed: file not opened for write "
606
0
                 "access.");
607
0
        return -1;
608
0
    }
609
610
0
    if (m_poCurObjBlock != nullptr || m_poSpIndex != nullptr)
611
0
    {
612
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
613
0
                 "SetQuickSpatialIndexMode() must be called before writing the "
614
0
                 "first object.");
615
0
        return -1;
616
0
    }
617
618
0
    m_bQuickSpatialIndexMode = bQuickSpatialIndexMode;
619
620
0
    return 0;
621
0
}
622
623
/************************************************************************/
624
/*                             PushBlock()                              */
625
/*                                                                      */
626
/*      Install a new block (object or spatial) as being current -      */
627
/*      whatever that means.  This method is only intended to ever      */
628
/*      be called from LoadNextMatchingObjectBlock().                   */
629
/************************************************************************/
630
631
TABRawBinBlock *TABMAPFile::PushBlock(int nFileOffset)
632
633
0
{
634
0
    TABRawBinBlock *poBlock = GetIndexObjectBlock(nFileOffset);
635
0
    if (poBlock == nullptr)
636
0
        return nullptr;
637
638
0
    if (poBlock->GetBlockType() == TABMAP_INDEX_BLOCK)
639
0
    {
640
0
        auto poIndex = std::unique_ptr<TABMAPIndexBlock>(
641
0
            cpl::down_cast<TABMAPIndexBlock *>(poBlock));
642
643
0
        if (m_poSpIndexLeaf == nullptr)
644
0
        {
645
0
            delete m_poSpIndex;
646
0
            m_poSpIndex = poIndex.release();
647
0
            m_poSpIndexLeaf = m_poSpIndex;
648
0
        }
649
0
        else
650
0
        {
651
0
            CPLAssert(
652
0
                m_poSpIndexLeaf->GetEntry(m_poSpIndexLeaf->GetCurChildIndex())
653
0
                    ->nBlockPtr == nFileOffset);
654
655
0
            m_poSpIndexLeaf->SetCurChild(std::move(poIndex),
656
0
                                         m_poSpIndexLeaf->GetCurChildIndex());
657
0
            m_poSpIndexLeaf = m_poSpIndexLeaf->GetCurChild();
658
0
        }
659
0
    }
660
0
    else
661
0
    {
662
0
        CPLAssert(poBlock->GetBlockType() == TABMAP_OBJECT_BLOCK);
663
664
0
        if (m_poCurObjBlock != nullptr)
665
0
            delete m_poCurObjBlock;
666
667
0
        m_poCurObjBlock = cpl::down_cast<TABMAPObjectBlock *>(poBlock);
668
669
0
        m_nCurObjPtr = nFileOffset;
670
0
        m_nCurObjType = TAB_GEOM_NONE;
671
0
        m_nCurObjId = -1;
672
0
    }
673
674
0
    return poBlock;
675
0
}
676
677
/************************************************************************/
678
/*                    LoadNextMatchingObjectBlock()                     */
679
/*                                                                      */
680
/*      Advance through the spatial indices till the next object        */
681
/*      block is loaded that matching the spatial query extents.        */
682
/************************************************************************/
683
684
int TABMAPFile::LoadNextMatchingObjectBlock(int bFirstObject)
685
686
0
{
687
    // If we are just starting, verify the stack is empty.
688
0
    if (bFirstObject)
689
0
    {
690
0
        CPLAssert(m_poSpIndexLeaf == nullptr);
691
692
        /* m_nFirstIndexBlock set to 0 means that there is no feature */
693
0
        if (m_poHeader->m_nFirstIndexBlock == 0)
694
0
            return FALSE;
695
696
0
        if (m_poSpIndex != nullptr)
697
0
        {
698
0
            m_poSpIndex->UnsetCurChild();
699
0
            m_poSpIndexLeaf = m_poSpIndex;
700
0
        }
701
0
        else
702
0
        {
703
0
            if (PushBlock(m_poHeader->m_nFirstIndexBlock) == nullptr)
704
0
                return FALSE;
705
706
0
            if (m_poSpIndex == nullptr)
707
0
            {
708
0
                CPLAssert(m_poCurObjBlock != nullptr);
709
0
                return TRUE;
710
0
            }
711
0
        }
712
0
    }
713
714
0
    while (m_poSpIndexLeaf != nullptr)
715
0
    {
716
0
        int iEntry = m_poSpIndexLeaf->GetCurChildIndex();
717
718
0
        if (iEntry >= m_poSpIndexLeaf->GetNumEntries() - 1)
719
0
        {
720
0
            TABMAPIndexBlock *poParent = m_poSpIndexLeaf->GetParentRef();
721
0
            if (m_poSpIndexLeaf == m_poSpIndex)
722
0
                m_poSpIndex->UnsetCurChild();
723
0
            m_poSpIndexLeaf = poParent;
724
725
0
            if (poParent != nullptr)
726
0
            {
727
0
                poParent->SetCurChild(nullptr, poParent->GetCurChildIndex());
728
0
            }
729
0
            continue;
730
0
        }
731
732
0
        m_poSpIndexLeaf->SetCurChild(nullptr, ++iEntry);
733
734
0
        TABMAPIndexEntry *psEntry = m_poSpIndexLeaf->GetEntry(iEntry);
735
0
        if (!psEntry)
736
0
        {
737
0
            CPLAssert(false);
738
0
            continue;
739
0
        }
740
0
        if (psEntry->XMax < m_XMinFilter || psEntry->YMax < m_YMinFilter ||
741
0
            psEntry->XMin > m_XMaxFilter || psEntry->YMin > m_YMaxFilter)
742
0
            continue;
743
744
0
        TABRawBinBlock *poBlock = PushBlock(psEntry->nBlockPtr);
745
0
        if (poBlock == nullptr)
746
0
            return FALSE;
747
0
        else if (poBlock->GetBlockType() == TABMAP_OBJECT_BLOCK)
748
0
            return TRUE;
749
0
        else
750
0
        {
751
            /* continue processing new index block */
752
0
        }
753
0
    }
754
755
0
    return false;
756
0
}
757
758
/************************************************************************/
759
/*                            ResetReading()                            */
760
/*                                                                      */
761
/*      Ensure that any resources related to a spatial traversal of     */
762
/*      the file are recovered, and the state reinitialized to the      */
763
/*      initial conditions.                                             */
764
/************************************************************************/
765
766
void TABMAPFile::ResetReading()
767
768
0
{
769
0
    if (m_bLastOpWasWrite)
770
0
        CommitObjAndCoordBlocks(FALSE);
771
772
0
    if (m_poSpIndex)
773
0
    {
774
0
        m_poSpIndex->UnsetCurChild();
775
0
    }
776
0
    m_poSpIndexLeaf = nullptr;
777
778
0
    m_bLastOpWasWrite = FALSE;
779
0
    m_bLastOpWasRead = FALSE;
780
0
}
781
782
/************************************************************************/
783
/*                          GetNextFeatureId()                          */
784
/*                                                                      */
785
/*      Fetch the next feature id based on a traversal of the           */
786
/*      spatial index.                                                  */
787
/************************************************************************/
788
789
int TABMAPFile::GetNextFeatureId(int nPrevId)
790
791
0
{
792
0
    if (m_bLastOpWasWrite)
793
0
    {
794
0
        CPLError(CE_Failure, CPLE_AppDefined,
795
0
                 "GetNextFeatureId() cannot be called after write operation");
796
0
        return -1;
797
0
    }
798
0
    if (m_eAccessMode == TABWrite)
799
0
    {
800
0
        if (ReOpenReadWrite() < 0)
801
0
            return -1;
802
0
    }
803
0
    m_bLastOpWasRead = TRUE;
804
805
    /* -------------------------------------------------------------------- */
806
    /*      m_fp is NULL when all geometry are NONE and/or there's          */
807
    /*          no .map file and/or there's no spatial indexes              */
808
    /* -------------------------------------------------------------------- */
809
0
    if (m_fp == nullptr)
810
0
        return -1;
811
812
0
    if (nPrevId == 0)
813
0
        nPrevId = -1;
814
815
    /* -------------------------------------------------------------------- */
816
    /*      This should always be true if we are being called properly.     */
817
    /* -------------------------------------------------------------------- */
818
0
    if (nPrevId != -1 && m_nCurObjId != nPrevId)
819
0
    {
820
0
        CPLError(CE_Failure, CPLE_AppDefined,
821
0
                 "TABMAPFile::GetNextFeatureId(%d) called out of sequence.",
822
0
                 nPrevId);
823
0
        return -1;
824
0
    }
825
826
0
    CPLAssert(nPrevId == -1 || m_poCurObjBlock != nullptr);
827
828
    /* -------------------------------------------------------------------- */
829
    /*      Ensure things are initialized properly if this is a request     */
830
    /*      for the first feature.                                          */
831
    /* -------------------------------------------------------------------- */
832
0
    if (nPrevId == -1)
833
0
    {
834
0
        m_nCurObjId = -1;
835
0
    }
836
837
    /* -------------------------------------------------------------------- */
838
    /*      Try to advance to the next object in the current object         */
839
    /*      block.                                                          */
840
    /* -------------------------------------------------------------------- */
841
0
    if (nPrevId == -1 || m_poCurObjBlock->AdvanceToNextObject(m_poHeader) == -1)
842
0
    {
843
        // If not, try to advance to the next object block, and get
844
        // first object from it.  Note that some object blocks actually
845
        // have no objects, so we may have to advance to additional
846
        // object blocks till we find a non-empty one.
847
0
        GBool bFirstCall = (nPrevId == -1);
848
0
        do
849
0
        {
850
0
            if (!LoadNextMatchingObjectBlock(bFirstCall))
851
0
                return -1;
852
853
0
            bFirstCall = FALSE;
854
0
        } while (m_poCurObjBlock->AdvanceToNextObject(m_poHeader) == -1);
855
0
    }
856
857
0
    m_nCurObjType = m_poCurObjBlock->GetCurObjectType();
858
0
    m_nCurObjId = m_poCurObjBlock->GetCurObjectId();
859
0
    m_nCurObjPtr = m_poCurObjBlock->GetStartAddress() +
860
0
                   m_poCurObjBlock->GetCurObjectOffset();
861
862
0
    CPLAssert(m_nCurObjId != -1);
863
864
0
    return m_nCurObjId;
865
0
}
866
867
/**********************************************************************
868
 *                   TABMAPFile::Int2Coordsys()
869
 *
870
 * Convert from long integer (internal) to coordinates system units
871
 * as defined in the file's coordsys clause.
872
 *
873
 * Note that the false easting/northing and the conversion factor from
874
 * datum to coordsys units are not included in the calculation.
875
 *
876
 * Returns 0 on success, -1 on error.
877
 **********************************************************************/
878
int TABMAPFile::Int2Coordsys(GInt32 nX, GInt32 nY, double &dX, double &dY)
879
10.4k
{
880
10.4k
    if (m_poHeader == nullptr)
881
0
        return -1;
882
883
10.4k
    return m_poHeader->Int2Coordsys(nX, nY, dX, dY);
884
10.4k
}
885
886
/**********************************************************************
887
 *                   TABMAPFile::Coordsys2Int()
888
 *
889
 * Convert from coordinates system units as defined in the file's
890
 * coordsys clause to long integer (internal) coordinates.
891
 *
892
 * Note that the false easting/northing and the conversion factor from
893
 * datum to coordsys units are not included in the calculation.
894
 *
895
 * Returns 0 on success, -1 on error.
896
 **********************************************************************/
897
int TABMAPFile::Coordsys2Int(double dX, double dY, GInt32 &nX, GInt32 &nY,
898
                             GBool bIgnoreOverflow /*=FALSE*/)
899
61.8k
{
900
61.8k
    if (m_poHeader == nullptr)
901
0
        return -1;
902
903
61.8k
    return m_poHeader->Coordsys2Int(dX, dY, nX, nY, bIgnoreOverflow);
904
61.8k
}
905
906
/**********************************************************************
907
 *                   TABMAPFile::Int2CoordsysDist()
908
 *
909
 * Convert a pair of X,Y size (or distance) values from long integer
910
 * (internal) to coordinates system units as defined in the file's coordsys
911
 * clause.
912
 *
913
 * The difference with Int2Coordsys() is that this function only applies
914
 * the scaling factor: it does not apply the displacement.
915
 *
916
 * Since the calculations on the X and Y values are independent, either
917
 * one can be omitted (i.e. passed as 0)
918
 *
919
 * Returns 0 on success, -1 on error.
920
 **********************************************************************/
921
int TABMAPFile::Int2CoordsysDist(GInt32 nX, GInt32 nY, double &dX, double &dY)
922
57
{
923
57
    if (m_poHeader == nullptr)
924
0
        return -1;
925
926
57
    return m_poHeader->Int2CoordsysDist(nX, nY, dX, dY);
927
57
}
928
929
/**********************************************************************
930
 *                   TABMAPFile::Coordsys2IntDist()
931
 *
932
 * Convert a pair of X,Y size (or distance) values from coordinates
933
 * system units as defined in the file's coordsys clause to long
934
 * integer (internal) coordinate units.
935
 *
936
 * The difference with Int2Coordsys() is that this function only applies
937
 * the scaling factor: it does not apply the displacement.
938
 *
939
 * Since the calculations on the X and Y values are independent, either
940
 * one can be omitted (i.e. passed as 0)
941
 *
942
 * Returns 0 on success, -1 on error.
943
 **********************************************************************/
944
int TABMAPFile::Coordsys2IntDist(double dX, double dY, GInt32 &nX, GInt32 &nY)
945
0
{
946
0
    if (m_poHeader == nullptr)
947
0
        return -1;
948
949
0
    return m_poHeader->Coordsys2IntDist(dX, dY, nX, nY);
950
0
}
951
952
/**********************************************************************
953
 *                   TABMAPFile::SetCoordsysBounds()
954
 *
955
 * Set projection coordinates bounds of the newly created dataset.
956
 *
957
 * This function must be called after creating a new dataset and before any
958
 * feature can be written to it.
959
 *
960
 * Returns 0 on success, -1 on error.
961
 **********************************************************************/
962
int TABMAPFile::SetCoordsysBounds(double dXMin, double dYMin, double dXMax,
963
                                  double dYMax)
964
226
{
965
226
    if (m_poHeader == nullptr)
966
0
        return -1;
967
968
226
    const int nStatus =
969
226
        m_poHeader->SetCoordsysBounds(dXMin, dYMin, dXMax, dYMax);
970
971
226
    if (nStatus == 0)
972
226
        ResetCoordFilter();
973
974
226
    return nStatus;
975
226
}
976
977
/**********************************************************************
978
 *                   TABMAPFile::GetMaxObjId()
979
 *
980
 * Return the value of the biggest valid object id.
981
 *
982
 * Note that object ids are positive and start at 1.
983
 *
984
 * Returns a value >= 0 on success, -1 on error.
985
 **********************************************************************/
986
GInt32 TABMAPFile::GetMaxObjId()
987
0
{
988
0
    if (m_poIdIndex)
989
0
        return m_poIdIndex->GetMaxObjId();
990
991
0
    return -1;
992
0
}
993
994
/**********************************************************************
995
 *                   TABMAPFile::MoveToObjId()
996
 *
997
 * Get ready to work with the object with the specified id.  The object
998
 * data pointer (inside m_poCurObjBlock) will be moved to the first byte
999
 * of data for this map object.
1000
 *
1001
 * The object type and id (i.e. table row number) will be accessible
1002
 * using GetCurObjType() and GetCurObjId().
1003
 *
1004
 * Note that object ids are positive and start at 1.
1005
 *
1006
 * Returns 0 on success, -1 on error.
1007
 **********************************************************************/
1008
int TABMAPFile::MoveToObjId(int nObjId)
1009
789k
{
1010
789k
    if (m_bLastOpWasWrite)
1011
0
    {
1012
0
        CPLError(CE_Failure, CPLE_AppDefined,
1013
0
                 "MoveToObjId() cannot be called after write operation");
1014
0
        return -1;
1015
0
    }
1016
789k
    if (m_eAccessMode == TABWrite)
1017
0
    {
1018
0
        if (ReOpenReadWrite() < 0)
1019
0
            return -1;
1020
0
    }
1021
789k
    m_bLastOpWasRead = TRUE;
1022
1023
    /*-----------------------------------------------------------------
1024
     * In non creation mode, since the .MAP/.ID are optional, if the
1025
     * file is not opened then we can still act as if one existed and
1026
     * make any object id look like a TAB_GEOM_NONE
1027
     *----------------------------------------------------------------*/
1028
789k
    if (m_fp == nullptr && m_eAccessMode != TABWrite)
1029
788k
    {
1030
788k
        CPLAssert(m_poIdIndex == nullptr && m_poCurObjBlock == nullptr);
1031
788k
        m_nCurObjPtr = 0;
1032
788k
        m_nCurObjId = nObjId;
1033
788k
        m_nCurObjType = TAB_GEOM_NONE;
1034
1035
788k
        return 0;
1036
788k
    }
1037
1038
1.41k
    if (m_poIdIndex == nullptr)
1039
0
    {
1040
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
1041
0
                 "MoveToObjId(): file not opened!");
1042
0
        m_nCurObjPtr = -1;
1043
0
        m_nCurObjId = -1;
1044
0
        m_nCurObjType = TAB_GEOM_UNSET;
1045
0
        return -1;
1046
0
    }
1047
1048
    /*-----------------------------------------------------------------
1049
     * Move map object pointer to the right location.  Fetch location
1050
     * from the index file, unless we are already pointing at it.
1051
     *----------------------------------------------------------------*/
1052
1.41k
    int nFileOffset =
1053
1.41k
        m_nCurObjId == nObjId ? m_nCurObjPtr : m_poIdIndex->GetObjPtr(nObjId);
1054
1055
1.41k
    if (nFileOffset != 0 && m_poCurObjBlock == nullptr)
1056
0
    {
1057
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
1058
0
                 "MoveToObjId(): no current object block!");
1059
0
        m_nCurObjPtr = -1;
1060
0
        m_nCurObjId = -1;
1061
0
        m_nCurObjType = TAB_GEOM_UNSET;
1062
0
        return -1;
1063
0
    }
1064
1065
1.41k
    if (nFileOffset == 0)
1066
62
    {
1067
        /*---------------------------------------------------------
1068
         * Object with no geometry... this is a valid case.
1069
         *--------------------------------------------------------*/
1070
62
        m_nCurObjPtr = 0;
1071
62
        m_nCurObjId = nObjId;
1072
62
        m_nCurObjType = TAB_GEOM_NONE;
1073
62
    }
1074
1.35k
    else if (m_poCurObjBlock->GotoByteInFile(nFileOffset, TRUE) == 0)
1075
1.29k
    {
1076
        /*-------------------------------------------------------------
1077
         * OK, it worked, read the object type and row id.
1078
         *------------------------------------------------------------*/
1079
1.29k
        m_nCurObjPtr = nFileOffset;
1080
1081
1.29k
        const GByte byVal = m_poCurObjBlock->ReadByte();
1082
1.29k
        if (IsValidObjType(byVal))
1083
1.27k
        {
1084
1.27k
            m_nCurObjType = static_cast<TABGeomType>(byVal);
1085
1.27k
        }
1086
18
        else
1087
18
        {
1088
18
            CPLError(
1089
18
                CE_Warning,
1090
18
                static_cast<CPLErrorNum>(TAB_WarningFeatureTypeNotSupported),
1091
18
                "Unsupported object type %d (0x%2.2x).  Feature will be "
1092
18
                "returned with NONE geometry.",
1093
18
                byVal, byVal);
1094
18
            m_nCurObjType = TAB_GEOM_NONE;
1095
18
        }
1096
1.29k
        m_nCurObjId = m_poCurObjBlock->ReadInt32();
1097
1098
        // Do a consistency check...
1099
1.29k
        if (m_nCurObjId != nObjId)
1100
26
        {
1101
26
            if (m_nCurObjId == (nObjId | 0x40000000))
1102
0
            {
1103
0
                CPLError(CE_Failure, CPLE_FileIO,
1104
0
                         "Object %d is marked as deleted in the .MAP file but "
1105
0
                         "not in the .ID file."
1106
0
                         "File may be corrupt.",
1107
0
                         nObjId);
1108
0
            }
1109
26
            else
1110
26
            {
1111
26
                CPLError(
1112
26
                    CE_Failure, CPLE_FileIO,
1113
26
                    "Object ID from the .ID file (%d) differs from the value "
1114
26
                    "in the .MAP file (%d).  File may be corrupt.",
1115
26
                    nObjId, m_nCurObjId);
1116
26
            }
1117
26
            m_nCurObjPtr = -1;
1118
26
            m_nCurObjId = -1;
1119
26
            m_nCurObjType = TAB_GEOM_UNSET;
1120
26
            return -1;
1121
26
        }
1122
1.29k
    }
1123
58
    else
1124
58
    {
1125
        /*---------------------------------------------------------
1126
         * Failed positioning input file... CPLError has been called.
1127
         *--------------------------------------------------------*/
1128
58
        m_nCurObjPtr = -1;
1129
58
        m_nCurObjId = -1;
1130
58
        m_nCurObjType = TAB_GEOM_UNSET;
1131
58
        return -1;
1132
58
    }
1133
1134
1.32k
    return 0;
1135
1.41k
}
1136
1137
/**********************************************************************
1138
 *                   TABMAPFile::MarkAsDeleted()
1139
 *
1140
 * Returns 0 on success, -1 on error.
1141
 **********************************************************************/
1142
int TABMAPFile::MarkAsDeleted()
1143
0
{
1144
0
    if (m_eAccessMode == TABRead)
1145
0
        return -1;
1146
1147
0
    if (m_nCurObjPtr <= 0)
1148
0
        return 0;
1149
1150
0
    int ret = 0;
1151
0
    if (m_nCurObjType != TAB_GEOM_NONE)
1152
0
    {
1153
        /* Goto offset for object id */
1154
0
        if (m_poCurObjBlock == nullptr ||
1155
0
            m_poCurObjBlock->GotoByteInFile(m_nCurObjPtr + 1, TRUE) != 0)
1156
0
            return -1;
1157
1158
        /* Mark object as deleted */
1159
0
        m_poCurObjBlock->WriteInt32(m_nCurObjId | 0x40000000);
1160
1161
0
        if (m_poCurObjBlock->CommitToFile() != 0)
1162
0
            ret = -1;
1163
0
    }
1164
1165
    /* Update index entry to reflect delete state as well */
1166
0
    if (m_poIdIndex->SetObjPtr(m_nCurObjId, 0) != 0)
1167
0
        ret = -1;
1168
1169
0
    m_nCurObjPtr = -1;
1170
0
    m_nCurObjId = -1;
1171
0
    m_nCurObjType = TAB_GEOM_UNSET;
1172
0
    m_bUpdated = TRUE;
1173
1174
0
    return ret;
1175
0
}
1176
1177
/**********************************************************************
1178
 *                   TABMAPFile::UpdateMapHeaderInfo()
1179
 *
1180
 * Update .map header information (counter of objects by type and minimum
1181
 * required version) in light of a new object to be written to the file.
1182
 *
1183
 * Called only by PrepareNewObj() and by the TABCollection class.
1184
 **********************************************************************/
1185
void TABMAPFile::UpdateMapHeaderInfo(TABGeomType nObjType)
1186
13.2k
{
1187
    /*-----------------------------------------------------------------
1188
     * Update count of objects by type in the header block
1189
     *----------------------------------------------------------------*/
1190
13.2k
    if (nObjType == TAB_GEOM_SYMBOL || nObjType == TAB_GEOM_FONTSYMBOL ||
1191
9.39k
        nObjType == TAB_GEOM_CUSTOMSYMBOL || nObjType == TAB_GEOM_MULTIPOINT ||
1192
9.39k
        nObjType == TAB_GEOM_V800_MULTIPOINT || nObjType == TAB_GEOM_SYMBOL_C ||
1193
9.39k
        nObjType == TAB_GEOM_FONTSYMBOL_C ||
1194
9.39k
        nObjType == TAB_GEOM_CUSTOMSYMBOL_C ||
1195
9.39k
        nObjType == TAB_GEOM_MULTIPOINT_C ||
1196
9.39k
        nObjType == TAB_GEOM_V800_MULTIPOINT_C)
1197
3.87k
    {
1198
3.87k
        m_poHeader->m_numPointObjects++;
1199
3.87k
    }
1200
9.39k
    else if (nObjType == TAB_GEOM_LINE || nObjType == TAB_GEOM_PLINE ||
1201
8.36k
             nObjType == TAB_GEOM_MULTIPLINE ||
1202
8.33k
             nObjType == TAB_GEOM_V450_MULTIPLINE ||
1203
8.33k
             nObjType == TAB_GEOM_V800_MULTIPLINE || nObjType == TAB_GEOM_ARC ||
1204
8.33k
             nObjType == TAB_GEOM_LINE_C || nObjType == TAB_GEOM_PLINE_C ||
1205
7.91k
             nObjType == TAB_GEOM_MULTIPLINE_C ||
1206
3.40k
             nObjType == TAB_GEOM_V450_MULTIPLINE_C ||
1207
3.40k
             nObjType == TAB_GEOM_V800_MULTIPLINE_C ||
1208
3.40k
             nObjType == TAB_GEOM_ARC_C)
1209
5.98k
    {
1210
5.98k
        m_poHeader->m_numLineObjects++;
1211
5.98k
    }
1212
3.40k
    else if (nObjType == TAB_GEOM_REGION || nObjType == TAB_GEOM_V450_REGION ||
1213
3.10k
             nObjType == TAB_GEOM_V800_REGION || nObjType == TAB_GEOM_RECT ||
1214
3.10k
             nObjType == TAB_GEOM_ROUNDRECT || nObjType == TAB_GEOM_ELLIPSE ||
1215
3.10k
             nObjType == TAB_GEOM_REGION_C ||
1216
0
             nObjType == TAB_GEOM_V450_REGION_C ||
1217
0
             nObjType == TAB_GEOM_V800_REGION_C ||
1218
0
             nObjType == TAB_GEOM_RECT_C || nObjType == TAB_GEOM_ROUNDRECT_C ||
1219
0
             nObjType == TAB_GEOM_ELLIPSE_C)
1220
3.40k
    {
1221
3.40k
        m_poHeader->m_numRegionObjects++;
1222
3.40k
    }
1223
0
    else if (nObjType == TAB_GEOM_TEXT || nObjType == TAB_GEOM_TEXT_C)
1224
0
    {
1225
0
        m_poHeader->m_numTextObjects++;
1226
0
    }
1227
1228
    /*-----------------------------------------------------------------
1229
     * Check for minimum TAB file version number
1230
     *----------------------------------------------------------------*/
1231
13.2k
    int nVersion = TAB_GEOM_GET_VERSION(nObjType);
1232
1233
13.2k
    if (nVersion > m_nMinTABVersion)
1234
0
    {
1235
0
        m_nMinTABVersion = nVersion;
1236
0
    }
1237
13.2k
}
1238
1239
/**********************************************************************
1240
 *                   TABMAPFile::PrepareNewObj()
1241
 *
1242
 * Get ready to write a new object described by poObjHdr (using the
1243
 * poObjHdr's m_nId (featureId), m_nType and IntMBR members which must
1244
 * have been set by the caller).
1245
 *
1246
 * Depending on whether "quick spatial index mode" is selected, we either:
1247
 *
1248
 * 1- Walk through the spatial index to find the best place to insert the
1249
 * new object, update the spatial index references, and prepare the object
1250
 * data block to be ready to write the object to it.
1251
 * ... or ...
1252
 * 2- prepare the current object data block to be ready to write the
1253
 * object to it. If the object block is full then it is inserted in the
1254
 * spatial index and committed to disk, and a new obj block is created.
1255
 *
1256
 * m_poCurObjBlock will be set to be ready to receive the new object, and
1257
 * a new block will be created if necessary (in which case the current
1258
 * block contents will be committed to disk, etc.)  The actual ObjHdr
1259
 * data won't be written to m_poCurObjBlock until CommitNewObj() is called.
1260
 *
1261
 * If this object type uses coordinate blocks, then the coordinate block
1262
 * will be prepared to receive coordinates.
1263
 *
1264
 * This function will also take care of updating the .ID index entry for
1265
 * the new object.
1266
 *
1267
 * Note that object ids are positive and start at 1.
1268
 *
1269
 * Returns 0 on success, -1 on error.
1270
 **********************************************************************/
1271
int TABMAPFile::PrepareNewObj(TABMAPObjHdr *poObjHdr)
1272
311k
{
1273
311k
    m_nCurObjPtr = -1;
1274
311k
    m_nCurObjId = -1;
1275
311k
    m_nCurObjType = TAB_GEOM_UNSET;
1276
1277
311k
    if (m_eAccessMode == TABRead || m_poIdIndex == nullptr ||
1278
311k
        m_poHeader == nullptr)
1279
0
    {
1280
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
1281
0
                 "PrepareNewObj() failed: file not opened for write access.");
1282
0
        return -1;
1283
0
    }
1284
1285
311k
    if (m_bLastOpWasRead)
1286
0
    {
1287
0
        m_bLastOpWasRead = FALSE;
1288
0
        if (m_poSpIndex)
1289
0
        {
1290
0
            m_poSpIndex->UnsetCurChild();
1291
0
        }
1292
0
    }
1293
1294
    /*-----------------------------------------------------------------
1295
     * For objects with no geometry, we just update the .ID file and return
1296
     *----------------------------------------------------------------*/
1297
311k
    if (poObjHdr->m_nType == TAB_GEOM_NONE)
1298
297k
    {
1299
297k
        m_nCurObjType = poObjHdr->m_nType;
1300
297k
        m_nCurObjId = poObjHdr->m_nId;
1301
297k
        m_nCurObjPtr = 0;
1302
297k
        m_poIdIndex->SetObjPtr(m_nCurObjId, 0);
1303
1304
297k
        return 0;
1305
297k
    }
1306
1307
    /*-----------------------------------------------------------------
1308
     * Update count of objects by type in the header block and minimum
1309
     * required version.
1310
     *----------------------------------------------------------------*/
1311
13.2k
    UpdateMapHeaderInfo(poObjHdr->m_nType);
1312
1313
    /*-----------------------------------------------------------------
1314
     * Depending on the selected spatial index mode, we will either insert
1315
     * new objects via the spatial index (slower write but results in optimal
1316
     * spatial index) or directly in the current ObjBlock (faster write
1317
     * but non-optimal spatial index)
1318
     *----------------------------------------------------------------*/
1319
13.2k
    if (!m_bQuickSpatialIndexMode)
1320
0
    {
1321
0
        if (PrepareNewObjViaSpatialIndex(poObjHdr) != 0)
1322
0
            return -1; /* Error already reported */
1323
0
    }
1324
13.2k
    else
1325
13.2k
    {
1326
13.2k
        if (PrepareNewObjViaObjBlock(poObjHdr) != 0)
1327
0
            return -1; /* Error already reported */
1328
13.2k
    }
1329
1330
    /*-----------------------------------------------------------------
1331
     * Prepare ObjBlock for this new object.
1332
     * Real data won't be written to the object block until CommitNewObj()
1333
     * is called.
1334
     *----------------------------------------------------------------*/
1335
13.2k
    m_nCurObjPtr = m_poCurObjBlock->PrepareNewObject(poObjHdr);
1336
13.2k
    if (m_nCurObjPtr < 0)
1337
0
    {
1338
0
        CPLError(CE_Failure, CPLE_FileIO,
1339
0
                 "Failed writing object header for feature id %d",
1340
0
                 poObjHdr->m_nId);
1341
0
        return -1;
1342
0
    }
1343
1344
13.2k
    m_nCurObjType = poObjHdr->m_nType;
1345
13.2k
    m_nCurObjId = poObjHdr->m_nId;
1346
1347
    /*-----------------------------------------------------------------
1348
     * Update .ID Index
1349
     *----------------------------------------------------------------*/
1350
13.2k
    m_poIdIndex->SetObjPtr(m_nCurObjId, m_nCurObjPtr);
1351
1352
    /*-----------------------------------------------------------------
1353
     * Prepare Coords block...
1354
     * create a new TABMAPCoordBlock if it was not done yet.
1355
     *----------------------------------------------------------------*/
1356
13.2k
    PrepareCoordBlock(m_nCurObjType, m_poCurObjBlock, &m_poCurCoordBlock);
1357
1358
13.2k
    if (CPLGetLastErrorType() == CE_Failure)
1359
0
        return -1;
1360
1361
13.2k
    m_bUpdated = TRUE;
1362
13.2k
    m_bLastOpWasWrite = TRUE;
1363
1364
13.2k
    return 0;
1365
13.2k
}
1366
1367
/**********************************************************************
1368
 *                   TABMAPFile::PrepareNewObjViaSpatialIndex()
1369
 *
1370
 * Used by TABMAPFile::PrepareNewObj() to walk through the spatial index
1371
 * to find the best place to insert the new object, update the spatial
1372
 * index references, and prepare the object data block to be ready to
1373
 * write the object to it.
1374
 *
1375
 * This method is used when "quick spatial index mode" is NOT selected,
1376
 * i.e. when we want to produce a file with an optimal spatial index
1377
 *
1378
 * Returns 0 on success, -1 on error.
1379
 **********************************************************************/
1380
int TABMAPFile::PrepareNewObjViaSpatialIndex(TABMAPObjHdr *poObjHdr)
1381
0
{
1382
0
    GInt32 nObjBlockForInsert = -1;
1383
1384
    /*-----------------------------------------------------------------
1385
     * Create spatial index if we don't have one yet.
1386
     * We do not create the index and object data blocks in the open()
1387
     * call because files that contained only "NONE" geometries ended up
1388
     * with empty object and spatial index blocks.
1389
     *----------------------------------------------------------------*/
1390
0
    if (m_poSpIndex == nullptr)
1391
0
    {
1392
        // Spatial Index not created yet...
1393
0
        m_poSpIndex = new TABMAPIndexBlock(m_eAccessMode);
1394
1395
0
        m_poSpIndex->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
1396
0
                                  m_oBlockManager.AllocNewBlock("INDEX"));
1397
0
        m_poSpIndex->SetMAPBlockManagerRef(&m_oBlockManager);
1398
1399
0
        if (m_eAccessMode == TABReadWrite &&
1400
0
            m_poHeader->m_nFirstIndexBlock != 0)
1401
0
        {
1402
            /* This can happen if the file created by MapInfo contains just */
1403
            /* a few objects */
1404
0
            TABRawBinBlock *poBlock =
1405
0
                GetIndexObjectBlock(m_poHeader->m_nFirstIndexBlock);
1406
0
            CPLAssert(poBlock != nullptr &&
1407
0
                      poBlock->GetBlockType() == TABMAP_OBJECT_BLOCK);
1408
0
            delete poBlock;
1409
1410
0
            if (m_poSpIndex->AddEntry(m_poHeader->m_nXMin, m_poHeader->m_nYMin,
1411
0
                                      m_poHeader->m_nXMax, m_poHeader->m_nYMax,
1412
0
                                      m_poHeader->m_nFirstIndexBlock) != 0)
1413
0
                return -1;
1414
1415
0
            delete m_poCurObjBlock;
1416
0
            m_poCurObjBlock = nullptr;
1417
0
            delete m_poCurCoordBlock;
1418
0
            m_poCurCoordBlock = nullptr;
1419
0
        }
1420
1421
0
        m_poHeader->m_nFirstIndexBlock = m_poSpIndex->GetNodeBlockPtr();
1422
1423
        /* We'll also need to create an object data block (later) */
1424
        // nObjBlockForInsert = -1;
1425
1426
0
        CPLAssert(m_poCurObjBlock == nullptr);
1427
0
    }
1428
0
    else
1429
    /*-----------------------------------------------------------------
1430
     * Search the spatial index to find the best place to insert this
1431
     * new object.
1432
     *----------------------------------------------------------------*/
1433
0
    {
1434
0
        nObjBlockForInsert = m_poSpIndex->ChooseLeafForInsert(
1435
0
            poObjHdr->m_nMinX, poObjHdr->m_nMinY, poObjHdr->m_nMaxX,
1436
0
            poObjHdr->m_nMaxY);
1437
0
        if (nObjBlockForInsert == -1)
1438
0
        {
1439
            /* ChooseLeafForInsert() should not fail unless file is corrupt*/
1440
0
            CPLError(CE_Failure, CPLE_AssertionFailed,
1441
0
                     "ChooseLeafForInsert() Failed?!?!");
1442
0
            return -1;
1443
0
        }
1444
0
    }
1445
1446
0
    if (nObjBlockForInsert == -1)
1447
0
    {
1448
        /*-------------------------------------------------------------
1449
         * Create a new object data block from scratch
1450
         *------------------------------------------------------------*/
1451
0
        m_poCurObjBlock = new TABMAPObjectBlock(TABReadWrite);
1452
1453
0
        int nBlockOffset = m_oBlockManager.AllocNewBlock("OBJECT");
1454
1455
0
        m_poCurObjBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
1456
0
                                      nBlockOffset);
1457
1458
        /*-------------------------------------------------------------
1459
         * Insert new object block in index, based on MBR of poObjHdr
1460
         *------------------------------------------------------------*/
1461
0
        if (m_poSpIndex->AddEntry(poObjHdr->m_nMinX, poObjHdr->m_nMinY,
1462
0
                                  poObjHdr->m_nMaxX, poObjHdr->m_nMaxY,
1463
0
                                  m_poCurObjBlock->GetStartAddress()) != 0)
1464
0
            return -1;
1465
1466
0
        m_poCurObjBlock->SetMBR(poObjHdr->m_nMinX, poObjHdr->m_nMinY,
1467
0
                                poObjHdr->m_nMaxX, poObjHdr->m_nMaxY);
1468
1469
0
        const int nNextDepth = m_poSpIndex->GetCurMaxDepth() + 1;
1470
0
        m_poHeader->m_nMaxSpIndexDepth = static_cast<GByte>(std::max(
1471
0
            static_cast<int>(m_poHeader->m_nMaxSpIndexDepth), nNextDepth));
1472
0
    }
1473
0
    else
1474
0
    {
1475
        /*-------------------------------------------------------------
1476
         * Load existing object and Coord blocks, unless we've already
1477
         * got the right object block in memory
1478
         *------------------------------------------------------------*/
1479
0
        if (m_poCurObjBlock &&
1480
0
            m_poCurObjBlock->GetStartAddress() != nObjBlockForInsert)
1481
0
        {
1482
            /* Got a block in memory but it is not the right one, flush it */
1483
0
            if (CommitObjAndCoordBlocks(TRUE) != 0)
1484
0
                return -1;
1485
0
        }
1486
1487
0
        if (m_poCurObjBlock == nullptr)
1488
0
        {
1489
0
            if (LoadObjAndCoordBlocks(nObjBlockForInsert) != 0)
1490
0
                return -1;
1491
0
        }
1492
1493
        /* If we have compressed objects, we don't want to change the center  */
1494
0
        m_poCurObjBlock->LockCenter();
1495
1496
        // Check if the ObjBlock know its MBR. If not (new block, or the current
1497
        // block was the good one but retrieved without the index), get the
1498
        // value from the index and set it.
1499
0
        GInt32 nMinX, nMinY, nMaxX, nMaxY;
1500
0
        m_poCurObjBlock->GetMBR(nMinX, nMinY, nMaxX, nMaxY);
1501
0
        if (nMinX > nMaxX)
1502
0
        {
1503
0
            m_poSpIndex->GetCurLeafEntryMBR(m_poCurObjBlock->GetStartAddress(),
1504
0
                                            nMinX, nMinY, nMaxX, nMaxY);
1505
0
            m_poCurObjBlock->SetMBR(nMinX, nMinY, nMaxX, nMaxY);
1506
0
        }
1507
0
    }
1508
1509
    /*-----------------------------------------------------------------
1510
     * Fetch new object size, make sure there is enough room in obj.
1511
     * block for new object, update spatial index and split if necessary.
1512
     *----------------------------------------------------------------*/
1513
0
    int nObjSize = m_poHeader->GetMapObjectSize(poObjHdr->m_nType);
1514
1515
    /*-----------------------------------------------------------------
1516
     * But first check if we can recover space from this block in case
1517
     * there are deleted objects in it.
1518
     *----------------------------------------------------------------*/
1519
0
    if (m_poCurObjBlock->GetNumUnusedBytes() < nObjSize)
1520
0
    {
1521
0
        std::vector<std::unique_ptr<TABMAPObjHdr>> apoSrcObjHdrs;
1522
0
        int nObjectSpace = 0;
1523
1524
        /* First pass to enumerate valid objects and compute their accumulated
1525
           required size. */
1526
0
        m_poCurObjBlock->Rewind();
1527
0
        while (auto poExistingObjHdr =
1528
0
                   TABMAPObjHdr::ReadNextObj(m_poCurObjBlock, m_poHeader))
1529
0
        {
1530
0
            nObjectSpace +=
1531
0
                m_poHeader->GetMapObjectSize(poExistingObjHdr->m_nType);
1532
0
            apoSrcObjHdrs.emplace_back(poExistingObjHdr);
1533
0
        }
1534
1535
        /* Check that there's really some place that can be recovered */
1536
0
        if (nObjectSpace < m_poHeader->m_nRegularBlockSize - 20 -
1537
0
                               m_poCurObjBlock->GetNumUnusedBytes())
1538
0
        {
1539
#ifdef DEBUG_VERBOSE
1540
            CPLDebug("MITAB",
1541
                     "Compacting block at offset %d, %d objects valid, "
1542
                     "recovering %d bytes",
1543
                     m_poCurObjBlock->GetStartAddress(),
1544
                     static_cast<int>(apoSrcObjHdrs.size()),
1545
                     (m_poHeader->m_nRegularBlockSize - 20 -
1546
                      m_poCurObjBlock->GetNumUnusedBytes()) -
1547
                         nObjectSpace);
1548
#endif
1549
0
            m_poCurObjBlock->ClearObjects();
1550
1551
0
            for (auto &poSrcObjHdrs : apoSrcObjHdrs)
1552
0
            {
1553
                /*-----------------------------------------------------------------
1554
                 * Prepare and Write ObjHdr to this ObjBlock
1555
                 *----------------------------------------------------------------*/
1556
0
                int nObjPtr =
1557
0
                    m_poCurObjBlock->PrepareNewObject(poSrcObjHdrs.get());
1558
0
                if (nObjPtr < 0 ||
1559
0
                    m_poCurObjBlock->CommitNewObject(poSrcObjHdrs.get()) != 0)
1560
0
                {
1561
0
                    CPLError(CE_Failure, CPLE_FileIO,
1562
0
                             "Failed writing object header for feature id %d",
1563
0
                             poSrcObjHdrs->m_nId);
1564
0
                    return -1;
1565
0
                }
1566
1567
                /*-----------------------------------------------------------------
1568
                 * Update .ID Index
1569
                 *----------------------------------------------------------------*/
1570
0
                m_poIdIndex->SetObjPtr(poSrcObjHdrs->m_nId, nObjPtr);
1571
0
            }
1572
0
        }
1573
0
    }
1574
1575
0
    if (m_poCurObjBlock->GetNumUnusedBytes() >= nObjSize)
1576
0
    {
1577
        /*-------------------------------------------------------------
1578
         * New object fits in current block, just update the spatial index
1579
         *------------------------------------------------------------*/
1580
0
        GInt32 nMinX, nMinY, nMaxX, nMaxY;
1581
0
        m_poCurObjBlock->GetMBR(nMinX, nMinY, nMaxX, nMaxY);
1582
1583
        /* Need to calculate the enlarged MBR that includes new object */
1584
0
        nMinX = std::min(nMinX, poObjHdr->m_nMinX);
1585
0
        nMinY = std::min(nMinY, poObjHdr->m_nMinY);
1586
0
        nMaxX = std::max(nMaxX, poObjHdr->m_nMaxX);
1587
0
        nMaxY = std::max(nMaxY, poObjHdr->m_nMaxY);
1588
1589
0
        m_poCurObjBlock->SetMBR(nMinX, nMinY, nMaxX, nMaxY);
1590
1591
0
        if (m_poSpIndex->UpdateLeafEntry(m_poCurObjBlock->GetStartAddress(),
1592
0
                                         nMinX, nMinY, nMaxX, nMaxY) != 0)
1593
0
            return -1;
1594
0
    }
1595
0
    else
1596
0
    {
1597
        /*-------------------------------------------------------------
1598
         * OK, the new object won't fit in the current block, need to split
1599
         * and update index.
1600
         * Split() does its job so that the current obj block will remain
1601
         * the best candidate to receive the new object. It also flushes
1602
         * everything to disk and will update m_poCurCoordBlock to point to
1603
         * the last coord block in the chain, ready to accept new data
1604
         *------------------------------------------------------------*/
1605
0
        auto poNewObjBlock = std::unique_ptr<TABMAPObjectBlock>(
1606
0
            SplitObjBlock(poObjHdr, nObjSize));
1607
1608
0
        if (poNewObjBlock == nullptr)
1609
0
            return -1; /* Split failed, error already reported. */
1610
1611
        /*-------------------------------------------------------------
1612
         * Update index with info about m_poCurObjectBlock *first*
1613
         * This is important since UpdateLeafEntry() needs the chain of
1614
         * index nodes preloaded by ChooseLeafEntry() in order to do its job
1615
         *------------------------------------------------------------*/
1616
0
        GInt32 nMinX = 0;
1617
0
        GInt32 nMinY = 0;
1618
0
        GInt32 nMaxX = 0;
1619
0
        GInt32 nMaxY = 0;
1620
0
        m_poCurObjBlock->GetMBR(nMinX, nMinY, nMaxX, nMaxY);
1621
0
        CPLAssert(nMinX <= nMaxX);
1622
1623
        /* Need to calculate the enlarged MBR that includes new object */
1624
0
        nMinX = std::min(nMinX, poObjHdr->m_nMinX);
1625
0
        nMinY = std::min(nMinY, poObjHdr->m_nMinY);
1626
0
        nMaxX = std::max(nMaxX, poObjHdr->m_nMaxX);
1627
0
        nMaxY = std::max(nMaxY, poObjHdr->m_nMaxY);
1628
1629
0
        m_poCurObjBlock->SetMBR(nMinX, nMinY, nMaxX, nMaxY);
1630
1631
0
        if (m_poSpIndex->UpdateLeafEntry(m_poCurObjBlock->GetStartAddress(),
1632
0
                                         nMinX, nMinY, nMaxX, nMaxY) != 0)
1633
0
            return -1;
1634
1635
        /*-------------------------------------------------------------
1636
         * Add new obj block to index
1637
         *------------------------------------------------------------*/
1638
0
        poNewObjBlock->GetMBR(nMinX, nMinY, nMaxX, nMaxY);
1639
0
        CPLAssert(nMinX <= nMaxX);
1640
1641
0
        if (m_poSpIndex->AddEntry(nMinX, nMinY, nMaxX, nMaxY,
1642
0
                                  poNewObjBlock->GetStartAddress()) != 0)
1643
0
            return -1;
1644
0
        const int nNextDepth = m_poSpIndex->GetCurMaxDepth() + 1;
1645
0
        m_poHeader->m_nMaxSpIndexDepth = static_cast<GByte>(std::max(
1646
0
            static_cast<int>(m_poHeader->m_nMaxSpIndexDepth), nNextDepth));
1647
1648
        /*-------------------------------------------------------------
1649
         * Implicitly delete second object block, no need to commit to file
1650
         *first since it is already been committed to disk by Split()
1651
         *------------------------------------------------------------*/
1652
0
    }
1653
1654
0
    return 0;
1655
0
}
1656
1657
/**********************************************************************
1658
 *                   TABMAPFile::PrepareNewObjViaObjBlock()
1659
 *
1660
 * Used by TABMAPFile::PrepareNewObj() to prepare the current object
1661
 * data block to be ready to write the object to it. If the object block
1662
 * is full then it is inserted in the spatial index and committed to disk,
1663
 * and a new obj block is created.
1664
 *
1665
 * This method is used when "quick spatial index mode" is selected,
1666
 * i.e. faster write, but non-optimal spatial index.
1667
 *
1668
 * Returns 0 on success, -1 on error.
1669
 **********************************************************************/
1670
int TABMAPFile::PrepareNewObjViaObjBlock(TABMAPObjHdr *poObjHdr)
1671
13.2k
{
1672
    /*-------------------------------------------------------------
1673
     * We will need an object block... check if it exists and
1674
     * create it if it has not been created yet (first time for this file).
1675
     * We do not create the object block in the open() call because
1676
     * files that contained only "NONE" geometries ended up with empty
1677
     * object and spatial index blocks.
1678
     * Note: A coord block will be created only if needed later.
1679
     *------------------------------------------------------------*/
1680
13.2k
    if (m_poCurObjBlock == nullptr)
1681
164
    {
1682
164
        m_poCurObjBlock = new TABMAPObjectBlock(m_eAccessMode);
1683
1684
164
        int nBlockOffset = m_oBlockManager.AllocNewBlock("OBJECT");
1685
1686
164
        m_poCurObjBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
1687
164
                                      nBlockOffset);
1688
1689
        // The reference to the first object block should
1690
        // actually go through the index blocks... this will be
1691
        // updated when file is closed.
1692
164
        m_poHeader->m_nFirstIndexBlock = nBlockOffset;
1693
164
    }
1694
1695
    /*-----------------------------------------------------------------
1696
     * Fetch new object size, make sure there is enough room in obj.
1697
     * block for new object, and save/create a new one if necessary.
1698
     *----------------------------------------------------------------*/
1699
13.2k
    const int nObjSize = m_poHeader->GetMapObjectSize(poObjHdr->m_nType);
1700
13.2k
    if (m_poCurObjBlock->GetNumUnusedBytes() < nObjSize)
1701
685
    {
1702
        /*-------------------------------------------------------------
1703
         * OK, the new object won't fit in the current block. Add the
1704
         * current block to the spatial index, commit it to disk and init
1705
         * a new block
1706
         *------------------------------------------------------------*/
1707
685
        CommitObjAndCoordBlocks(FALSE);
1708
1709
685
        if (m_poCurObjBlock->InitNewBlock(
1710
685
                m_fp, m_poHeader->m_nRegularBlockSize,
1711
685
                m_oBlockManager.AllocNewBlock("OBJECT")) != 0)
1712
0
            return -1; /* Error already reported */
1713
1714
        /*-------------------------------------------------------------
1715
         * Coord block has been committed to disk but not deleted.
1716
         * Delete it to require the creation of a new coord block chain
1717
         * as needed.
1718
         *-------------------------------------------------------------*/
1719
685
        if (m_poCurCoordBlock)
1720
685
        {
1721
685
            delete m_poCurCoordBlock;
1722
685
            m_poCurCoordBlock = nullptr;
1723
685
        }
1724
685
    }
1725
1726
13.2k
    return 0;
1727
13.2k
}
1728
1729
/**********************************************************************
1730
 *                   TABMAPFile::CommitNewObj()
1731
 *
1732
 * Commit object header data to the ObjBlock. Should be called after
1733
 * PrepareNewObj, once all members of the ObjHdr have been set.
1734
 *
1735
 * Returns 0 on success, -1 on error.
1736
 **********************************************************************/
1737
int TABMAPFile::CommitNewObj(TABMAPObjHdr *poObjHdr)
1738
310k
{
1739
    // Nothing to do for NONE objects
1740
310k
    if (poObjHdr->m_nType == TAB_GEOM_NONE)
1741
297k
    {
1742
297k
        return 0;
1743
297k
    }
1744
1745
    /* Update this now so that PrepareCoordBlock() doesn't try to old an older
1746
     */
1747
    /* block */
1748
12.4k
    if (m_poCurCoordBlock != nullptr)
1749
11.3k
        m_poCurObjBlock->AddCoordBlockRef(m_poCurCoordBlock->GetStartAddress());
1750
1751
    /* So that GetExtent() is up-to-date */
1752
12.4k
    if (m_poSpIndex != nullptr)
1753
10.0k
    {
1754
10.0k
        m_poSpIndex->GetMBR(m_poHeader->m_nXMin, m_poHeader->m_nYMin,
1755
10.0k
                            m_poHeader->m_nXMax, m_poHeader->m_nYMax);
1756
10.0k
    }
1757
1758
12.4k
    return m_poCurObjBlock->CommitNewObject(poObjHdr);
1759
310k
}
1760
1761
/**********************************************************************
1762
 *                   TABMAPFile::CommitObjAndCoordBlocks()
1763
 *
1764
 * Commit the TABMAPObjBlock and TABMAPCoordBlock to disk.
1765
 *
1766
 * The objects are deleted from memory if bDeleteObjects==TRUE.
1767
 *
1768
 * Returns 0 on success, -1 on error.
1769
 **********************************************************************/
1770
int TABMAPFile::CommitObjAndCoordBlocks(GBool bDeleteObjects /*=FALSE*/)
1771
916
{
1772
916
    int nStatus = 0;
1773
1774
    /*-----------------------------------------------------------------
1775
     * First check that a objBlock has been created.  It is possible to have
1776
     * no object block in files that contain only "NONE" geometries.
1777
     *----------------------------------------------------------------*/
1778
916
    if (m_poCurObjBlock == nullptr)
1779
67
        return 0;
1780
1781
849
    if (m_eAccessMode == TABRead)
1782
0
    {
1783
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
1784
0
                 "CommitObjAndCoordBlocks() failed: file not opened for write "
1785
0
                 "access.");
1786
0
        return -1;
1787
0
    }
1788
1789
849
    if (!m_bLastOpWasWrite)
1790
0
    {
1791
0
        if (bDeleteObjects)
1792
0
        {
1793
0
            delete m_poCurCoordBlock;
1794
0
            m_poCurCoordBlock = nullptr;
1795
0
            delete m_poCurObjBlock;
1796
0
            m_poCurObjBlock = nullptr;
1797
0
        }
1798
0
        return 0;
1799
0
    }
1800
849
    m_bLastOpWasWrite = FALSE;
1801
1802
    /*-----------------------------------------------------------------
1803
     * We need to flush the coord block if there was one
1804
     * since a list of coord blocks can belong to only one obj. block
1805
     *----------------------------------------------------------------*/
1806
849
    if (m_poCurCoordBlock)
1807
834
    {
1808
        // Update the m_nMaxCoordBufSize member in the header block
1809
        //
1810
834
        int nTotalCoordSize = m_poCurCoordBlock->GetNumBlocksInChain() *
1811
834
                              m_poHeader->m_nRegularBlockSize;
1812
834
        if (nTotalCoordSize > m_poHeader->m_nMaxCoordBufSize)
1813
166
            m_poHeader->m_nMaxCoordBufSize = nTotalCoordSize;
1814
1815
        // Update the references to this coord block in the MAPObjBlock
1816
        //
1817
834
        m_poCurObjBlock->AddCoordBlockRef(m_poCurCoordBlock->GetStartAddress());
1818
834
        nStatus = m_poCurCoordBlock->CommitToFile();
1819
1820
834
        if (bDeleteObjects)
1821
0
        {
1822
0
            delete m_poCurCoordBlock;
1823
0
            m_poCurCoordBlock = nullptr;
1824
0
        }
1825
834
    }
1826
1827
    /*-----------------------------------------------------------------
1828
     * Commit the obj block
1829
     *----------------------------------------------------------------*/
1830
849
    if (nStatus == 0)
1831
849
    {
1832
849
        nStatus = m_poCurObjBlock->CommitToFile();
1833
849
    }
1834
1835
    /*-----------------------------------------------------------------
1836
     * Update the spatial index ** only in "quick spatial index" mode **
1837
     * In the (default) optimized spatial index mode, the spatial index
1838
     * is already maintained up to date as part of inserting the objects in
1839
     * PrepareNewObj().
1840
     *
1841
     * Spatial index will be created here if it was not done yet.
1842
     *----------------------------------------------------------------*/
1843
849
    if (nStatus == 0 && m_bQuickSpatialIndexMode)
1844
849
    {
1845
849
        if (m_poSpIndex == nullptr)
1846
164
        {
1847
            // Spatial Index not created yet...
1848
164
            m_poSpIndex = new TABMAPIndexBlock(m_eAccessMode);
1849
1850
164
            m_poSpIndex->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
1851
164
                                      m_oBlockManager.AllocNewBlock("INDEX"));
1852
164
            m_poSpIndex->SetMAPBlockManagerRef(&m_oBlockManager);
1853
1854
164
            m_poHeader->m_nFirstIndexBlock = m_poSpIndex->GetNodeBlockPtr();
1855
164
        }
1856
1857
849
        GInt32 nXMin, nYMin, nXMax, nYMax;
1858
849
        m_poCurObjBlock->GetMBR(nXMin, nYMin, nXMax, nYMax);
1859
849
        nStatus = m_poSpIndex->AddEntry(nXMin, nYMin, nXMax, nYMax,
1860
849
                                        m_poCurObjBlock->GetStartAddress());
1861
1862
849
        const int nNextDepth = m_poSpIndex->GetCurMaxDepth() + 1;
1863
849
        m_poHeader->m_nMaxSpIndexDepth = static_cast<GByte>(std::max(
1864
849
            static_cast<int>(m_poHeader->m_nMaxSpIndexDepth), nNextDepth));
1865
849
    }
1866
1867
    /*-----------------------------------------------------------------
1868
     * Delete obj block only if requested
1869
     *----------------------------------------------------------------*/
1870
849
    if (bDeleteObjects)
1871
0
    {
1872
0
        delete m_poCurObjBlock;
1873
0
        m_poCurObjBlock = nullptr;
1874
0
    }
1875
1876
849
    return nStatus;
1877
849
}
1878
1879
/**********************************************************************
1880
 *                   TABMAPFile::LoadObjAndCoordBlocks()
1881
 *
1882
 * Load the TABMAPObjBlock at specified address and corresponding
1883
 * TABMAPCoordBlock, ready to write new objects to them.
1884
 *
1885
 * It is assumed that pre-existing m_poCurObjBlock and m_poCurCoordBlock
1886
 * have been flushed to disk already using CommitObjAndCoordBlocks()
1887
 *
1888
 * Returns 0 on success, -1 on error.
1889
 **********************************************************************/
1890
int TABMAPFile::LoadObjAndCoordBlocks(GInt32 nBlockPtr)
1891
0
{
1892
    /*-----------------------------------------------------------------
1893
     * In Write mode, if an object block is already in memory then flush it
1894
     *----------------------------------------------------------------*/
1895
0
    if (m_eAccessMode != TABRead && m_poCurObjBlock != nullptr)
1896
0
    {
1897
0
        int nStatus = CommitObjAndCoordBlocks(TRUE);
1898
0
        if (nStatus != 0)
1899
0
            return nStatus;
1900
0
    }
1901
1902
    /*-----------------------------------------------------------------
1903
     * Load Obj Block
1904
     *----------------------------------------------------------------*/
1905
0
    TABRawBinBlock *poBlock = TABCreateMAPBlockFromFile(
1906
0
        m_fp, nBlockPtr, m_poHeader->m_nRegularBlockSize, TRUE, TABReadWrite);
1907
0
    if (poBlock != nullptr && poBlock->GetBlockClass() == TABMAP_OBJECT_BLOCK)
1908
0
    {
1909
0
        m_poCurObjBlock = cpl::down_cast<TABMAPObjectBlock *>(poBlock);
1910
0
        poBlock = nullptr;
1911
0
    }
1912
0
    else
1913
0
    {
1914
0
        CPLError(CE_Failure, CPLE_FileIO,
1915
0
                 "LoadObjAndCoordBlocks() failed for object block at %d.",
1916
0
                 nBlockPtr);
1917
0
        return -1;
1918
0
    }
1919
1920
    /*-----------------------------------------------------------------
1921
     * Load the last coord block in the chain
1922
     *----------------------------------------------------------------*/
1923
0
    if (m_poCurObjBlock->GetLastCoordBlockAddress() == 0)
1924
0
    {
1925
0
        m_poCurCoordBlock = nullptr;
1926
0
        return 0;
1927
0
    }
1928
1929
0
    poBlock = TABCreateMAPBlockFromFile(
1930
0
        m_fp, m_poCurObjBlock->GetLastCoordBlockAddress(),
1931
0
        m_poHeader->m_nRegularBlockSize, TRUE, TABReadWrite);
1932
0
    if (poBlock != nullptr && poBlock->GetBlockClass() == TABMAP_COORD_BLOCK)
1933
0
    {
1934
0
        m_poCurCoordBlock = cpl::down_cast<TABMAPCoordBlock *>(poBlock);
1935
0
        m_poCurCoordBlock->SetMAPBlockManagerRef(&m_oBlockManager);
1936
0
        poBlock = nullptr;
1937
0
    }
1938
0
    else
1939
0
    {
1940
0
        CPLError(CE_Failure, CPLE_FileIO,
1941
0
                 "LoadObjAndCoordBlocks() failed for coord block at %d.",
1942
0
                 m_poCurObjBlock->GetLastCoordBlockAddress());
1943
0
        return -1;
1944
0
    }
1945
1946
0
    return 0;
1947
0
}
1948
1949
/**********************************************************************
1950
 *                   TABMAPFile::SplitObjBlock()
1951
 *
1952
 * Split m_poCurObjBlock using Guttman algorithm.
1953
 *
1954
 * SplitObjBlock() doe its job so that the current obj block will remain
1955
 * the best candidate to receive the new object to add. It also flushes
1956
 * everything to disk and will update m_poCurCoordBlock to point to the
1957
 * last coord block in the chain, ready to accept new data
1958
 *
1959
 * Updates to the spatial index are left to the caller.
1960
 *
1961
 * Returns the TABMAPObjBlock of the second block for use by the caller
1962
 * in updating the spatial index, or NULL in case of error.
1963
 **********************************************************************/
1964
TABMAPObjectBlock *TABMAPFile::SplitObjBlock(TABMAPObjHdr *poObjHdrToAdd,
1965
                                             int nSizeOfObjToAdd)
1966
0
{
1967
0
    std::vector<std::unique_ptr<TABMAPObjHdr>> apoSrcObjHdrs;
1968
1969
    /*-----------------------------------------------------------------
1970
     * Read all object headers
1971
     *----------------------------------------------------------------*/
1972
0
    m_poCurObjBlock->Rewind();
1973
0
    while (auto poObjHdr =
1974
0
               TABMAPObjHdr::ReadNextObj(m_poCurObjBlock, m_poHeader))
1975
0
    {
1976
0
        apoSrcObjHdrs.emplace_back(poObjHdr);
1977
0
    }
1978
    /* PickSeedsForSplit (reasonably) assumes at least 2 nodes */
1979
0
    CPLAssert(apoSrcObjHdrs.size() > 1);
1980
1981
    /*-----------------------------------------------------------------
1982
     * Reset current obj and coord block
1983
     *----------------------------------------------------------------*/
1984
0
    GInt32 nFirstSrcCoordBlock = m_poCurObjBlock->GetFirstCoordBlockAddress();
1985
1986
0
    m_poCurObjBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
1987
0
                                  m_poCurObjBlock->GetStartAddress());
1988
1989
0
    std::unique_ptr<TABMAPCoordBlock> poSrcCoordBlock(m_poCurCoordBlock);
1990
0
    m_poCurCoordBlock = nullptr;
1991
1992
    /*-----------------------------------------------------------------
1993
     * Create new obj and coord block
1994
     *----------------------------------------------------------------*/
1995
0
    auto poNewObjBlock = std::make_unique<TABMAPObjectBlock>(m_eAccessMode);
1996
0
    poNewObjBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
1997
0
                                m_oBlockManager.AllocNewBlock("OBJECT"));
1998
1999
    /* Use existing center of other block in case we have compressed objects
2000
       and freeze it */
2001
0
    poNewObjBlock->SetCenterFromOtherBlock(m_poCurObjBlock);
2002
2003
    /* Coord block will be alloc'd automatically*/
2004
0
    TABMAPCoordBlock *poNewCoordBlock = nullptr;
2005
2006
    /*-----------------------------------------------------------------
2007
     * Pick Seeds for each block
2008
     *----------------------------------------------------------------*/
2009
0
    std::vector<TABMAPIndexEntry> asSrcEntries;
2010
0
    asSrcEntries.reserve(apoSrcObjHdrs.size());
2011
0
    for (const auto &poSrcObjHdrs : apoSrcObjHdrs)
2012
0
    {
2013
0
        TABMAPIndexEntry sEntry;
2014
0
        sEntry.nBlockPtr = 0;
2015
0
        sEntry.XMin = poSrcObjHdrs->m_nMinX;
2016
0
        sEntry.YMin = poSrcObjHdrs->m_nMinY;
2017
0
        sEntry.XMax = poSrcObjHdrs->m_nMaxX;
2018
0
        sEntry.YMax = poSrcObjHdrs->m_nMaxY;
2019
0
        asSrcEntries.emplace_back(sEntry);
2020
0
    }
2021
2022
0
    int nSeed1, nSeed2;
2023
0
    TABMAPIndexBlock::PickSeedsForSplit(
2024
0
        asSrcEntries.data(), static_cast<int>(asSrcEntries.size()), -1,
2025
0
        poObjHdrToAdd->m_nMinX, poObjHdrToAdd->m_nMinY, poObjHdrToAdd->m_nMaxX,
2026
0
        poObjHdrToAdd->m_nMaxY, nSeed1, nSeed2);
2027
2028
    /*-----------------------------------------------------------------
2029
     * Assign the seeds to their respective block
2030
     *----------------------------------------------------------------*/
2031
    // Insert nSeed1 in this block
2032
0
    if (MoveObjToBlock(apoSrcObjHdrs[nSeed1].get(), poSrcCoordBlock.get(),
2033
0
                       m_poCurObjBlock, &m_poCurCoordBlock) <= 0)
2034
0
    {
2035
0
        return nullptr;
2036
0
    }
2037
2038
    // Move nSeed2 to 2nd block
2039
0
    if (MoveObjToBlock(apoSrcObjHdrs[nSeed2].get(), poSrcCoordBlock.get(),
2040
0
                       poNewObjBlock.get(), &poNewCoordBlock) <= 0)
2041
0
    {
2042
0
        return nullptr;
2043
0
    }
2044
2045
    /*-----------------------------------------------------------------
2046
     * Go through the rest of the entries and assign them to one
2047
     * of the 2 blocks
2048
     *
2049
     * Criteria is minimal area difference.
2050
     * Resolve ties by adding the entry to the block with smaller total
2051
     * area, then to the one with fewer entries, then to either.
2052
     *----------------------------------------------------------------*/
2053
0
    for (int iEntry = 0; iEntry < static_cast<int>(apoSrcObjHdrs.size());
2054
0
         iEntry++)
2055
0
    {
2056
0
        if (iEntry == nSeed1 || iEntry == nSeed2)
2057
0
            continue;
2058
2059
0
        TABMAPObjHdr *poObjHdr = apoSrcObjHdrs[iEntry].get();
2060
2061
0
        int nObjSize = m_poHeader->GetMapObjectSize(poObjHdr->m_nType);
2062
2063
        // If one of the two blocks is almost full then all remaining
2064
        // entries should go to the other block
2065
0
        if (m_poCurObjBlock->GetNumUnusedBytes() < nObjSize + nSizeOfObjToAdd)
2066
0
        {
2067
0
            if (MoveObjToBlock(poObjHdr, poSrcCoordBlock.get(),
2068
0
                               poNewObjBlock.get(), &poNewCoordBlock) <= 0)
2069
0
                return nullptr;
2070
0
            continue;
2071
0
        }
2072
0
        else if (poNewObjBlock->GetNumUnusedBytes() <
2073
0
                 nObjSize + nSizeOfObjToAdd)
2074
0
        {
2075
0
            if (MoveObjToBlock(poObjHdr, poSrcCoordBlock.get(), m_poCurObjBlock,
2076
0
                               &m_poCurCoordBlock) <= 0)
2077
0
                return nullptr;
2078
0
            continue;
2079
0
        }
2080
2081
        // Decide which of the two blocks to put this entry in
2082
0
        GInt32 nXMin, nYMin, nXMax, nYMax;
2083
0
        m_poCurObjBlock->GetMBR(nXMin, nYMin, nXMax, nYMax);
2084
0
        CPLAssert(nXMin <= nXMax);
2085
0
        double dAreaDiff1 = TABMAPIndexBlock::ComputeAreaDiff(
2086
0
            nXMin, nYMin, nXMax, nYMax, poObjHdr->m_nMinX, poObjHdr->m_nMinY,
2087
0
            poObjHdr->m_nMaxX, poObjHdr->m_nMaxY);
2088
2089
0
        poNewObjBlock->GetMBR(nXMin, nYMin, nXMax, nYMax);
2090
0
        CPLAssert(nXMin <= nXMax);
2091
0
        double dAreaDiff2 = TABMAPIndexBlock::ComputeAreaDiff(
2092
0
            nXMin, nYMin, nXMax, nYMax, poObjHdr->m_nMinX, poObjHdr->m_nMinY,
2093
0
            poObjHdr->m_nMaxX, poObjHdr->m_nMaxY);
2094
2095
0
        if (dAreaDiff1 < dAreaDiff2)
2096
0
        {
2097
            // This entry stays in this block
2098
0
            if (MoveObjToBlock(poObjHdr, poSrcCoordBlock.get(), m_poCurObjBlock,
2099
0
                               &m_poCurCoordBlock) <= 0)
2100
0
                return nullptr;
2101
0
        }
2102
0
        else
2103
0
        {
2104
            // This entry goes to new block
2105
0
            if (MoveObjToBlock(poObjHdr, poSrcCoordBlock.get(),
2106
0
                               poNewObjBlock.get(), &poNewCoordBlock) <= 0)
2107
0
                return nullptr;
2108
0
        }
2109
0
    }
2110
2111
    /*-----------------------------------------------------------------
2112
     * Delete second coord block if one was created
2113
     * Refs to coord block were kept up to date by MoveObjToBlock()
2114
     * We just need to commit to file and delete the object now.
2115
     *----------------------------------------------------------------*/
2116
0
    if (poNewCoordBlock)
2117
0
    {
2118
0
        if (poNewCoordBlock->CommitToFile() != 0)
2119
0
        {
2120
0
            return nullptr;
2121
0
        }
2122
0
        delete poNewCoordBlock;
2123
0
    }
2124
2125
    /*-----------------------------------------------------------------
2126
     * Release unused coord. data blocks
2127
     *----------------------------------------------------------------*/
2128
0
    if (poSrcCoordBlock)
2129
0
    {
2130
0
        if (poSrcCoordBlock->GetStartAddress() != nFirstSrcCoordBlock)
2131
0
        {
2132
0
            if (poSrcCoordBlock->GotoByteInFile(nFirstSrcCoordBlock, TRUE) != 0)
2133
0
            {
2134
0
                return nullptr;
2135
0
            }
2136
0
        }
2137
2138
0
        int nNextCoordBlock = poSrcCoordBlock->GetNextCoordBlock();
2139
0
        while (poSrcCoordBlock != nullptr)
2140
0
        {
2141
            // Mark this block as deleted
2142
0
            if (poSrcCoordBlock->CommitAsDeleted(
2143
0
                    m_oBlockManager.GetFirstGarbageBlock()) != 0)
2144
0
            {
2145
0
                return nullptr;
2146
0
            }
2147
0
            m_oBlockManager.PushGarbageBlockAsFirst(
2148
0
                poSrcCoordBlock->GetStartAddress());
2149
2150
            // Advance to next
2151
0
            if (nNextCoordBlock > 0)
2152
0
            {
2153
0
                if (poSrcCoordBlock->GotoByteInFile(nNextCoordBlock, TRUE) != 0)
2154
0
                    return nullptr;
2155
2156
0
                nNextCoordBlock = poSrcCoordBlock->GetNextCoordBlock();
2157
0
            }
2158
0
            else
2159
0
            {
2160
                // end of chain
2161
0
                poSrcCoordBlock.reset();
2162
0
            }
2163
0
        }
2164
0
    }
2165
2166
0
    if (poNewObjBlock->CommitToFile() != 0)
2167
0
        return nullptr;
2168
2169
0
    return poNewObjBlock.release();
2170
0
}
2171
2172
/**********************************************************************
2173
 *                   TABMAPFile::MoveObjToBlock()
2174
 *
2175
 * Moves an object and its coord data to a new ObjBlock. Used when
2176
 * splitting Obj Blocks.
2177
 *
2178
 * May update the value of ppoCoordBlock if a new coord block had to
2179
 * be created.
2180
 *
2181
 * Returns the address where new object is stored on success, -1 on error.
2182
 **********************************************************************/
2183
int TABMAPFile::MoveObjToBlock(TABMAPObjHdr *poObjHdr,
2184
                               TABMAPCoordBlock *poSrcCoordBlock,
2185
                               TABMAPObjectBlock *poDstObjBlock,
2186
                               TABMAPCoordBlock **ppoDstCoordBlock)
2187
0
{
2188
    /*-----------------------------------------------------------------
2189
     * Copy Coord data if applicable
2190
     * We use a temporary TABFeature object to handle the reading/writing
2191
     * of coord block data.
2192
     *----------------------------------------------------------------*/
2193
0
    if (m_poHeader->MapObjectUsesCoordBlock(poObjHdr->m_nType))
2194
0
    {
2195
0
        TABMAPObjHdrWithCoord *poObjHdrCoord =
2196
0
            cpl::down_cast<TABMAPObjHdrWithCoord *>(poObjHdr);
2197
0
        OGRFeatureDefn *poDummyDefn = new OGRFeatureDefn;
2198
        // Ref count defaults to 0... set it to 1
2199
0
        poDummyDefn->Reference();
2200
2201
0
        TABFeature *poFeature =
2202
0
            TABFeature::CreateFromMapInfoType(poObjHdr->m_nType, poDummyDefn);
2203
2204
0
        if (PrepareCoordBlock(poObjHdrCoord->m_nType, poDstObjBlock,
2205
0
                              ppoDstCoordBlock) != 0)
2206
0
            return -1;
2207
2208
0
        GInt32 nSrcCoordPtr = poObjHdrCoord->m_nCoordBlockPtr;
2209
2210
        /* Copy Coord data
2211
         * poObjHdrCoord->m_nCoordBlockPtr will be set by WriteGeometry...
2212
         * We pass second arg to GotoByteInFile() to force reading from file
2213
         * if nSrcCoordPtr is not in current block
2214
         */
2215
0
        if (poSrcCoordBlock->GotoByteInFile(nSrcCoordPtr, TRUE) != 0 ||
2216
0
            poFeature->ReadGeometryFromMAPFile(this, poObjHdr,
2217
0
                                               TRUE /* bCoordDataOnly */,
2218
0
                                               &poSrcCoordBlock) != 0 ||
2219
0
            poFeature->WriteGeometryToMAPFile(this, poObjHdr,
2220
0
                                              TRUE /* bCoordDataOnly */,
2221
0
                                              ppoDstCoordBlock) != 0)
2222
0
        {
2223
0
            delete poFeature;
2224
0
            delete poDummyDefn;
2225
0
            return -1;
2226
0
        }
2227
2228
        // Update the references to dest coord block in the MAPObjBlock
2229
        // in case new block has been alloc'd since PrepareCoordBlock()
2230
        //
2231
0
        poDstObjBlock->AddCoordBlockRef((*ppoDstCoordBlock)->GetStartAddress());
2232
        /* Cleanup */
2233
0
        delete poFeature;
2234
0
        poDummyDefn->Release();
2235
0
    }
2236
2237
    /*-----------------------------------------------------------------
2238
     * Prepare and Write ObjHdr to this ObjBlock
2239
     *----------------------------------------------------------------*/
2240
0
    int nObjPtr = poDstObjBlock->PrepareNewObject(poObjHdr);
2241
0
    if (nObjPtr < 0 || poDstObjBlock->CommitNewObject(poObjHdr) != 0)
2242
0
    {
2243
0
        CPLError(CE_Failure, CPLE_FileIO,
2244
0
                 "Failed writing object header for feature id %d",
2245
0
                 poObjHdr->m_nId);
2246
0
        return -1;
2247
0
    }
2248
2249
    /*-----------------------------------------------------------------
2250
     * Update .ID Index
2251
     *----------------------------------------------------------------*/
2252
0
    m_poIdIndex->SetObjPtr(poObjHdr->m_nId, nObjPtr);
2253
2254
0
    return nObjPtr;
2255
0
}
2256
2257
/**********************************************************************
2258
 *                   TABMAPFile::PrepareCoordBlock()
2259
 *
2260
 * Prepare the coord block to receive an object of specified type if one
2261
 * is needed, and update corresponding members in ObjBlock.
2262
 *
2263
 * May update the value of ppoCoordBlock and Returns 0 on success, -1 on error.
2264
 **********************************************************************/
2265
int TABMAPFile::PrepareCoordBlock(int nObjType, TABMAPObjectBlock *poObjBlock,
2266
                                  TABMAPCoordBlock **ppoCoordBlock)
2267
13.2k
{
2268
2269
    /*-----------------------------------------------------------------
2270
     * Prepare Coords block...
2271
     * create a new TABMAPCoordBlock if it was not done yet.
2272
     * Note that in write mode, TABCollections require read/write access
2273
     * to the coord block.
2274
     *----------------------------------------------------------------*/
2275
13.2k
    if (m_poHeader->MapObjectUsesCoordBlock(nObjType))
2276
9.14k
    {
2277
9.14k
        if (*ppoCoordBlock == nullptr)
2278
834
        {
2279
834
            *ppoCoordBlock = new TABMAPCoordBlock(
2280
834
                m_eAccessMode == TABWrite ? TABReadWrite : m_eAccessMode);
2281
834
            (*ppoCoordBlock)
2282
834
                ->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
2283
834
                               m_oBlockManager.AllocNewBlock("COORD"));
2284
834
            (*ppoCoordBlock)->SetMAPBlockManagerRef(&m_oBlockManager);
2285
2286
            // Set the references to this coord block in the MAPObjBlock
2287
834
            poObjBlock->AddCoordBlockRef((*ppoCoordBlock)->GetStartAddress());
2288
834
        }
2289
        /* If we are not at the end of the chain of coordinate blocks, then */
2290
        /* reload us */
2291
8.31k
        else if ((*ppoCoordBlock)->GetStartAddress() !=
2292
8.31k
                 poObjBlock->GetLastCoordBlockAddress())
2293
0
        {
2294
0
            TABRawBinBlock *poBlock = TABCreateMAPBlockFromFile(
2295
0
                m_fp, poObjBlock->GetLastCoordBlockAddress(),
2296
0
                m_poHeader->m_nRegularBlockSize, TRUE, TABReadWrite);
2297
0
            if (poBlock != nullptr &&
2298
0
                poBlock->GetBlockClass() == TABMAP_COORD_BLOCK)
2299
0
            {
2300
0
                delete *ppoCoordBlock;
2301
0
                *ppoCoordBlock = cpl::down_cast<TABMAPCoordBlock *>(poBlock);
2302
0
                (*ppoCoordBlock)->SetMAPBlockManagerRef(&m_oBlockManager);
2303
0
            }
2304
0
            else
2305
0
            {
2306
0
                delete poBlock;
2307
0
                CPLError(
2308
0
                    CE_Failure, CPLE_FileIO,
2309
0
                    "LoadObjAndCoordBlocks() failed for coord block at %d.",
2310
0
                    poObjBlock->GetLastCoordBlockAddress());
2311
0
                return -1;
2312
0
            }
2313
0
        }
2314
2315
9.14k
        if ((*ppoCoordBlock)->GetNumUnusedBytes() < 4)
2316
3
        {
2317
3
            int nNewBlockOffset = m_oBlockManager.AllocNewBlock("COORD");
2318
3
            (*ppoCoordBlock)->SetNextCoordBlock(nNewBlockOffset);
2319
3
            CPL_IGNORE_RET_VAL((*ppoCoordBlock)->CommitToFile());
2320
3
            (*ppoCoordBlock)
2321
3
                ->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
2322
3
                               nNewBlockOffset);
2323
3
            poObjBlock->AddCoordBlockRef((*ppoCoordBlock)->GetStartAddress());
2324
3
        }
2325
2326
        // Make sure read/write pointer is at the end of the block
2327
9.14k
        (*ppoCoordBlock)->SeekEnd();
2328
2329
9.14k
        if (CPLGetLastErrorType() == CE_Failure)
2330
0
            return -1;
2331
9.14k
    }
2332
2333
13.2k
    return 0;
2334
13.2k
}
2335
2336
/**********************************************************************
2337
 *                   TABMAPFile::GetCurObjType()
2338
 *
2339
 * Return the MapInfo object type of the object that the m_poCurObjBlock
2340
 * is pointing to.  This value is set after a call to MoveToObjId().
2341
 *
2342
 * Returns a value >= 0 on success, -1 on error.
2343
 **********************************************************************/
2344
TABGeomType TABMAPFile::GetCurObjType()
2345
817k
{
2346
817k
    return m_nCurObjType;
2347
817k
}
2348
2349
/**********************************************************************
2350
 *                   TABMAPFile::GetCurObjId()
2351
 *
2352
 * Return the MapInfo object id of the object that the m_poCurObjBlock
2353
 * is pointing to.  This value is set after a call to MoveToObjId().
2354
 *
2355
 * Returns a value >= 0 on success, -1 on error.
2356
 **********************************************************************/
2357
int TABMAPFile::GetCurObjId()
2358
28.1k
{
2359
28.1k
    return m_nCurObjId;
2360
28.1k
}
2361
2362
/**********************************************************************
2363
 *                   TABMAPFile::GetCurObjBlock()
2364
 *
2365
 * Return the m_poCurObjBlock.  If MoveToObjId() has previously been
2366
 * called then m_poCurObjBlock points to the beginning of the current
2367
 * object data.
2368
 *
2369
 * Returns a reference to an object owned by this TABMAPFile object, or
2370
 * NULL on error.
2371
 **********************************************************************/
2372
TABMAPObjectBlock *TABMAPFile::GetCurObjBlock()
2373
28.1k
{
2374
28.1k
    return m_poCurObjBlock;
2375
28.1k
}
2376
2377
/**********************************************************************
2378
 *                   TABMAPFile::GetCurCoordBlock()
2379
 *
2380
 * Return the m_poCurCoordBlock.  This function should be used after
2381
 * PrepareNewObj() to get the reference to the coord block that has
2382
 * just been initialized.
2383
 *
2384
 * Returns a reference to an object owned by this TABMAPFile object, or
2385
 * NULL on error.
2386
 **********************************************************************/
2387
TABMAPCoordBlock *TABMAPFile::GetCurCoordBlock()
2388
9.14k
{
2389
9.14k
    return m_poCurCoordBlock;
2390
9.14k
}
2391
2392
/**********************************************************************
2393
 *                   TABMAPFile::GetCoordBlock()
2394
 *
2395
 * Return a TABMAPCoordBlock object ready to read coordinates from it.
2396
 * The block that contains nFileOffset will automatically be
2397
 * loaded, and if nFileOffset is the beginning of a new block then the
2398
 * pointer will be moved to the beginning of the data.
2399
 *
2400
 * The contents of the returned object is only valid until the next call
2401
 * to GetCoordBlock().
2402
 *
2403
 * Returns a reference to an object owned by this TABMAPFile object, or
2404
 * NULL on error.
2405
 **********************************************************************/
2406
TABMAPCoordBlock *TABMAPFile::GetCoordBlock(int nFileOffset)
2407
171
{
2408
171
    if (m_poCurCoordBlock == nullptr)
2409
52
    {
2410
52
        m_poCurCoordBlock = new TABMAPCoordBlock(m_eAccessMode);
2411
52
        m_poCurCoordBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize);
2412
52
        m_poCurCoordBlock->SetMAPBlockManagerRef(&m_oBlockManager);
2413
52
    }
2414
2415
    /*-----------------------------------------------------------------
2416
     * Use GotoByteInFile() to go to the requested location.  This will
2417
     * force loading the block if necessary and reading its header.
2418
     * If nFileOffset is at the beginning of the requested block, then
2419
     * we make sure to move the read pointer past the 8 bytes header
2420
     * to be ready to read coordinates data
2421
     *----------------------------------------------------------------*/
2422
171
    if (m_poCurCoordBlock->GotoByteInFile(nFileOffset, TRUE) != 0)
2423
12
    {
2424
        // Failed... an error has already been reported.
2425
12
        return nullptr;
2426
12
    }
2427
2428
159
    if (nFileOffset % m_poHeader->m_nRegularBlockSize == 0)
2429
2
        m_poCurCoordBlock->GotoByteInBlock(8);  // Skip Header
2430
2431
159
    return m_poCurCoordBlock;
2432
171
}
2433
2434
/**********************************************************************
2435
 *                   TABMAPFile::GetHeaderBlock()
2436
 *
2437
 * Return a reference to the MAP file's header block.
2438
 *
2439
 * The returned pointer is a reference to an object owned by this TABMAPFile
2440
 * object and should not be deleted by the caller.
2441
 *
2442
 * Return NULL if file has not been opened yet.
2443
 **********************************************************************/
2444
TABMAPHeaderBlock *TABMAPFile::GetHeaderBlock()
2445
5.76k
{
2446
5.76k
    return m_poHeader;
2447
5.76k
}
2448
2449
/**********************************************************************
2450
 *                   TABMAPFile::GetIDFileRef()
2451
 *
2452
 * Return a reference to the .ID file attached to this .MAP file
2453
 *
2454
 * The returned pointer is a reference to an object owned by this TABMAPFile
2455
 * object and should not be deleted by the caller.
2456
 *
2457
 * Return NULL if file has not been opened yet.
2458
 **********************************************************************/
2459
TABIDFile *TABMAPFile::GetIDFileRef()
2460
0
{
2461
0
    return m_poIdIndex;
2462
0
}
2463
2464
/**********************************************************************
2465
 *                   TABMAPFile::GetIndexBlock()
2466
 *
2467
 * Return a reference to the requested index or object block..
2468
 *
2469
 * Ownership of the returned block is turned over to the caller, who should
2470
 * delete it when no longer needed.  The type of the block can be determined
2471
 * with the GetBlockType() method.
2472
 *
2473
 * @param nFileOffset the offset in the map file of the spatial index
2474
 * block or object block to load.
2475
 *
2476
 * @return The requested TABMAPIndexBlock, TABMAPObjectBlock or NULL if the
2477
 * read fails for some reason.
2478
 **********************************************************************/
2479
TABRawBinBlock *TABMAPFile::GetIndexObjectBlock(int nFileOffset)
2480
0
{
2481
    /*----------------------------------------------------------------
2482
     * Read from the file
2483
     *---------------------------------------------------------------*/
2484
0
    GByte *pabyData =
2485
0
        static_cast<GByte *>(CPLMalloc(m_poHeader->m_nRegularBlockSize));
2486
2487
0
    if (VSIFSeekL(m_fp, static_cast<vsi_l_offset>(nFileOffset), SEEK_SET) !=
2488
0
            0 ||
2489
0
        static_cast<int>(VSIFReadL(pabyData, sizeof(GByte),
2490
0
                                   m_poHeader->m_nRegularBlockSize, m_fp)) !=
2491
0
            m_poHeader->m_nRegularBlockSize)
2492
0
    {
2493
0
        CPLError(CE_Failure, CPLE_FileIO,
2494
0
                 "GetIndexBlock() failed reading %d bytes at offset %d.",
2495
0
                 m_poHeader->m_nRegularBlockSize, nFileOffset);
2496
0
        CPLFree(pabyData);
2497
0
        return nullptr;
2498
0
    }
2499
2500
    /* -------------------------------------------------------------------- */
2501
    /*      Create and initialize depending on the block type.              */
2502
    /* -------------------------------------------------------------------- */
2503
0
    int nBlockType = pabyData[0];
2504
0
    TABRawBinBlock *poBlock = nullptr;
2505
2506
0
    if (nBlockType == TABMAP_INDEX_BLOCK)
2507
0
    {
2508
0
        TABMAPIndexBlock *poIndexBlock = new TABMAPIndexBlock(m_eAccessMode);
2509
0
        poBlock = poIndexBlock;
2510
0
        poIndexBlock->SetMAPBlockManagerRef(&m_oBlockManager);
2511
0
    }
2512
0
    else
2513
0
        poBlock = new TABMAPObjectBlock(m_eAccessMode);
2514
2515
0
    poBlock->InitBlockFromData(pabyData, m_poHeader->m_nRegularBlockSize,
2516
0
                               m_poHeader->m_nRegularBlockSize, FALSE, m_fp,
2517
0
                               nFileOffset);
2518
2519
0
    return poBlock;
2520
0
}
2521
2522
/**********************************************************************
2523
 *                   TABMAPFile::InitDrawingTools()
2524
 *
2525
 * Init the drawing tools for this file.
2526
 *
2527
 * In Read mode, this will load the drawing tools from the file.
2528
 *
2529
 * In Write mode, this function will init an empty the tool def table.
2530
 *
2531
 * Returns 0 on success, -1 on error.
2532
 **********************************************************************/
2533
int TABMAPFile::InitDrawingTools()
2534
244
{
2535
244
    int nStatus = 0;
2536
2537
244
    if (m_poHeader == nullptr)
2538
0
        return -1;  // File not opened yet!
2539
2540
    /*-------------------------------------------------------------
2541
     * We want to perform this initialization only once
2542
     *------------------------------------------------------------*/
2543
244
    if (m_poToolDefTable != nullptr)
2544
0
        return 0;
2545
2546
    /*-------------------------------------------------------------
2547
     * Create a new ToolDefTable... no more initialization is required
2548
     * unless we want to read tool blocks from file.
2549
     *------------------------------------------------------------*/
2550
244
    m_poToolDefTable = new TABToolDefTable;
2551
2552
244
    if ((m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite) &&
2553
80
        m_poHeader->m_nFirstToolBlock != 0)
2554
71
    {
2555
71
        TABMAPToolBlock *poBlock = new TABMAPToolBlock(TABRead);
2556
71
        poBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize);
2557
2558
        /*-------------------------------------------------------------
2559
         * Use GotoByteInFile() to go to the first block's location.  This will
2560
         * force loading the block if necessary and reading its header.
2561
         * Also make sure to move the read pointer past the 8 bytes header
2562
         * to be ready to read drawing tools data
2563
         *------------------------------------------------------------*/
2564
71
        if (poBlock->GotoByteInFile(m_poHeader->m_nFirstToolBlock) != 0)
2565
50
        {
2566
            // Failed... an error has already been reported.
2567
50
            delete poBlock;
2568
50
            return -1;
2569
50
        }
2570
2571
21
        poBlock->GotoByteInBlock(8);
2572
2573
21
        nStatus = m_poToolDefTable->ReadAllToolDefs(poBlock);
2574
21
        delete poBlock;
2575
21
    }
2576
2577
194
    return nStatus;
2578
244
}
2579
2580
/**********************************************************************
2581
 *                   TABMAPFile::CommitDrawingTools()
2582
 *
2583
 * Write the drawing tools for this file.
2584
 *
2585
 * This function applies only to write access mode.
2586
 *
2587
 * Returns 0 on success, -1 on error.
2588
 **********************************************************************/
2589
int TABMAPFile::CommitDrawingTools()
2590
231
{
2591
231
    int nStatus = 0;
2592
2593
231
    if (m_eAccessMode == TABRead || m_poHeader == nullptr)
2594
0
    {
2595
0
        CPLError(
2596
0
            CE_Failure, CPLE_AssertionFailed,
2597
0
            "CommitDrawingTools() failed: file not opened for write access.");
2598
0
        return -1;
2599
0
    }
2600
2601
231
    if (m_poToolDefTable == nullptr ||
2602
164
        (m_poToolDefTable->GetNumPen() + m_poToolDefTable->GetNumBrushes() +
2603
164
         m_poToolDefTable->GetNumFonts() + m_poToolDefTable->GetNumSymbols()) ==
2604
164
            0)
2605
67
    {
2606
67
        return 0;  // Nothing to do!
2607
67
    }
2608
2609
    /*-------------------------------------------------------------
2610
     * Create a new TABMAPToolBlock and update header fields
2611
     *------------------------------------------------------------*/
2612
164
    TABMAPToolBlock *poBlock = new TABMAPToolBlock(m_eAccessMode);
2613
164
    if (m_poHeader->m_nFirstToolBlock != 0)
2614
0
        poBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
2615
0
                              m_poHeader->m_nFirstToolBlock);
2616
164
    else
2617
164
        poBlock->InitNewBlock(m_fp, m_poHeader->m_nRegularBlockSize,
2618
164
                              m_oBlockManager.AllocNewBlock("TOOL"));
2619
164
    poBlock->SetMAPBlockManagerRef(&m_oBlockManager);
2620
2621
164
    m_poHeader->m_nFirstToolBlock = poBlock->GetStartAddress();
2622
2623
164
    m_poHeader->m_numPenDefs =
2624
164
        static_cast<GByte>(m_poToolDefTable->GetNumPen());
2625
164
    m_poHeader->m_numBrushDefs =
2626
164
        static_cast<GByte>(m_poToolDefTable->GetNumBrushes());
2627
164
    m_poHeader->m_numFontDefs =
2628
164
        static_cast<GByte>(m_poToolDefTable->GetNumFonts());
2629
164
    m_poHeader->m_numSymbolDefs =
2630
164
        static_cast<GByte>(m_poToolDefTable->GetNumSymbols());
2631
2632
    /*-------------------------------------------------------------
2633
     * Do the actual work and delete poBlock
2634
     * (Note that poBlock will have already been committed to the file
2635
     * by WriteAllToolDefs() )
2636
     *------------------------------------------------------------*/
2637
164
    nStatus = m_poToolDefTable->WriteAllToolDefs(poBlock);
2638
2639
164
    m_poHeader->m_numMapToolBlocks =
2640
164
        static_cast<GByte>(poBlock->GetNumBlocksInChain());
2641
2642
164
    delete poBlock;
2643
2644
164
    return nStatus;
2645
231
}
2646
2647
/**********************************************************************
2648
 *                   TABMAPFile::ReadPenDef()
2649
 *
2650
 * Fill the TABPenDef structure with the definition of the specified pen
2651
 * index... (1-based pen index)
2652
 *
2653
 * If nPenIndex==0 or is invalid, then the structure is cleared.
2654
 *
2655
 * Returns 0 on success, -1 on error (i.e. Pen not found).
2656
 **********************************************************************/
2657
int TABMAPFile::ReadPenDef(int nPenIndex, TABPenDef *psDef)
2658
433
{
2659
433
    if (m_poToolDefTable == nullptr && InitDrawingTools() != 0)
2660
10
        return -1;
2661
2662
423
    TABPenDef *psTmp = nullptr;
2663
423
    if (psDef && m_poToolDefTable &&
2664
423
        (psTmp = m_poToolDefTable->GetPenDefRef(nPenIndex)) != nullptr)
2665
117
    {
2666
117
        *psDef = *psTmp;
2667
117
    }
2668
306
    else if (psDef)
2669
306
    {
2670
        /* Init to MapInfo default */
2671
306
        static const TABPenDef csDefaultPen = MITAB_PEN_DEFAULT;
2672
306
        *psDef = csDefaultPen;
2673
306
        return -1;
2674
306
    }
2675
117
    return 0;
2676
423
}
2677
2678
/**********************************************************************
2679
 *                   TABMAPFile::WritePenDef()
2680
 *
2681
 * Write a Pen Tool to the map file and return the pen index that has
2682
 * been attributed to this Pen tool definition, or -1 if something went
2683
 * wrong
2684
 *
2685
 * Note that the returned index is a 1-based index.  A value of 0
2686
 * indicates "none" in MapInfo.
2687
2688
 * Returns a value >= 0 on success, -1 on error
2689
 **********************************************************************/
2690
int TABMAPFile::WritePenDef(TABPenDef *psDef)
2691
8.52k
{
2692
8.52k
    if (psDef == nullptr ||
2693
8.52k
        (m_poToolDefTable == nullptr && InitDrawingTools() != 0) ||
2694
8.52k
        m_poToolDefTable == nullptr)
2695
0
    {
2696
0
        return -1;
2697
0
    }
2698
2699
8.52k
    return m_poToolDefTable->AddPenDefRef(psDef);
2700
8.52k
}
2701
2702
/**********************************************************************
2703
 *                   TABMAPFile::ReadBrushDef()
2704
 *
2705
 * Fill the TABBrushDef structure with the definition of the specified Brush
2706
 * index... (1-based Brush index)
2707
 *
2708
 * If nBrushIndex==0 or is invalid, then the structure is cleared.
2709
 *
2710
 * Returns 0 on success, -1 on error (i.e. Brush not found).
2711
 **********************************************************************/
2712
int TABMAPFile::ReadBrushDef(int nBrushIndex, TABBrushDef *psDef)
2713
176
{
2714
176
    if (m_poToolDefTable == nullptr && InitDrawingTools() != 0)
2715
0
        return -1;
2716
2717
176
    TABBrushDef *psTmp = nullptr;
2718
176
    if (psDef && m_poToolDefTable &&
2719
176
        (psTmp = m_poToolDefTable->GetBrushDefRef(nBrushIndex)) != nullptr)
2720
57
    {
2721
57
        *psDef = *psTmp;
2722
57
    }
2723
119
    else if (psDef)
2724
119
    {
2725
        /* Init to MapInfo default */
2726
119
        static const TABBrushDef csDefaultBrush = MITAB_BRUSH_DEFAULT;
2727
119
        *psDef = csDefaultBrush;
2728
119
        return -1;
2729
119
    }
2730
57
    return 0;
2731
176
}
2732
2733
/**********************************************************************
2734
 *                   TABMAPFile::WriteBrushDef()
2735
 *
2736
 * Write a Brush Tool to the map file and return the Brush index that has
2737
 * been attributed to this Brush tool definition, or -1 if something went
2738
 * wrong
2739
 *
2740
 * Note that the returned index is a 1-based index.  A value of 0
2741
 * indicates "none" in MapInfo.
2742
2743
 * Returns a value >= 0 on success, -1 on error
2744
 **********************************************************************/
2745
int TABMAPFile::WriteBrushDef(TABBrushDef *psDef)
2746
2.53k
{
2747
2.53k
    if (psDef == nullptr ||
2748
2.53k
        (m_poToolDefTable == nullptr && InitDrawingTools() != 0) ||
2749
2.53k
        m_poToolDefTable == nullptr)
2750
0
    {
2751
0
        return -1;
2752
0
    }
2753
2754
2.53k
    return m_poToolDefTable->AddBrushDefRef(psDef);
2755
2.53k
}
2756
2757
/**********************************************************************
2758
 *                   TABMAPFile::ReadFontDef()
2759
 *
2760
 * Fill the TABFontDef structure with the definition of the specified Font
2761
 * index... (1-based Font index)
2762
 *
2763
 * If nFontIndex==0 or is invalid, then the structure is cleared.
2764
 *
2765
 * Returns 0 on success, -1 on error (i.e. Font not found).
2766
 **********************************************************************/
2767
int TABMAPFile::ReadFontDef(int nFontIndex, TABFontDef *psDef)
2768
127
{
2769
127
    if (m_poToolDefTable == nullptr && InitDrawingTools() != 0)
2770
0
        return -1;
2771
2772
127
    TABFontDef *psTmp = nullptr;
2773
127
    if (psDef && m_poToolDefTable &&
2774
127
        (psTmp = m_poToolDefTable->GetFontDefRef(nFontIndex)) != nullptr)
2775
22
    {
2776
22
        *psDef = *psTmp;
2777
22
    }
2778
105
    else if (psDef)
2779
105
    {
2780
        /* Init to MapInfo default */
2781
105
        static const TABFontDef csDefaultFont = MITAB_FONT_DEFAULT;
2782
105
        *psDef = csDefaultFont;
2783
105
        return -1;
2784
105
    }
2785
22
    return 0;
2786
127
}
2787
2788
/**********************************************************************
2789
 *                   TABMAPFile::WriteFontDef()
2790
 *
2791
 * Write a Font Tool to the map file and return the Font index that has
2792
 * been attributed to this Font tool definition, or -1 if something went
2793
 * wrong
2794
 *
2795
 * Note that the returned index is a 1-based index.  A value of 0
2796
 * indicates "none" in MapInfo.
2797
2798
 * Returns a value >= 0 on success, -1 on error
2799
 **********************************************************************/
2800
int TABMAPFile::WriteFontDef(TABFontDef *psDef)
2801
0
{
2802
0
    if (psDef == nullptr ||
2803
0
        (m_poToolDefTable == nullptr && InitDrawingTools() != 0) ||
2804
0
        m_poToolDefTable == nullptr)
2805
0
    {
2806
0
        return -1;
2807
0
    }
2808
2809
0
    return m_poToolDefTable->AddFontDefRef(psDef);
2810
0
}
2811
2812
/**********************************************************************
2813
 *                   TABMAPFile::ReadSymbolDef()
2814
 *
2815
 * Fill the TABSymbolDef structure with the definition of the specified Symbol
2816
 * index... (1-based Symbol index)
2817
 *
2818
 * If nSymbolIndex==0 or is invalid, then the structure is cleared.
2819
 *
2820
 * Returns 0 on success, -1 on error (i.e. Symbol not found).
2821
 **********************************************************************/
2822
int TABMAPFile::ReadSymbolDef(int nSymbolIndex, TABSymbolDef *psDef)
2823
142
{
2824
142
    if (m_poToolDefTable == nullptr && InitDrawingTools() != 0)
2825
42
        return -1;
2826
2827
100
    TABSymbolDef *psTmp = nullptr;
2828
100
    if (psDef && m_poToolDefTable &&
2829
100
        (psTmp = m_poToolDefTable->GetSymbolDefRef(nSymbolIndex)) != nullptr)
2830
29
    {
2831
29
        *psDef = *psTmp;
2832
29
    }
2833
71
    else if (psDef)
2834
71
    {
2835
        /* Init to MapInfo default */
2836
71
        static const TABSymbolDef csDefaultSymbol = MITAB_SYMBOL_DEFAULT;
2837
71
        *psDef = csDefaultSymbol;
2838
71
        return -1;
2839
71
    }
2840
29
    return 0;
2841
100
}
2842
2843
/**********************************************************************
2844
 *                   TABMAPFile::WriteSymbolDef()
2845
 *
2846
 * Write a Symbol Tool to the map file and return the Symbol index that has
2847
 * been attributed to this Symbol tool definition, or -1 if something went
2848
 * wrong
2849
 *
2850
 * Note that the returned index is a 1-based index.  A value of 0
2851
 * indicates "none" in MapInfo.
2852
2853
 * Returns a value >= 0 on success, -1 on error
2854
 **********************************************************************/
2855
int TABMAPFile::WriteSymbolDef(TABSymbolDef *psDef)
2856
3.87k
{
2857
3.87k
    if (psDef == nullptr ||
2858
3.87k
        (m_poToolDefTable == nullptr && InitDrawingTools() != 0) ||
2859
3.87k
        m_poToolDefTable == nullptr)
2860
0
    {
2861
0
        return -1;
2862
0
    }
2863
2864
3.87k
    return m_poToolDefTable->AddSymbolDefRef(psDef);
2865
3.87k
}
2866
2867
static void ORDER_MIN_MAX(double &min, double &max)
2868
2.90k
{
2869
2.90k
    if (max < min)
2870
81
        std::swap(min, max);
2871
2.90k
}
2872
2873
static void ORDER_MIN_MAX(int &min, int &max)
2874
2.90k
{
2875
2.90k
    if (max < min)
2876
436
        std::swap(min, max);
2877
2.90k
}
2878
2879
/**********************************************************************
2880
 *                   TABMAPFile::SetCoordFilter()
2881
 *
2882
 * Set the MBR of the area of interest... only objects that at least
2883
 * overlap with that area will be returned.
2884
 *
2885
 * @param sMin minimum x/y the file's projection coord.
2886
 * @param sMax maximum x/y the file's projection coord.
2887
 **********************************************************************/
2888
void TABMAPFile::SetCoordFilter(TABVertex sMin, TABVertex sMax)
2889
0
{
2890
0
    m_sMinFilter = sMin;
2891
0
    m_sMaxFilter = sMax;
2892
2893
0
    Coordsys2Int(sMin.x, sMin.y, m_XMinFilter, m_YMinFilter, TRUE);
2894
0
    Coordsys2Int(sMax.x, sMax.y, m_XMaxFilter, m_YMaxFilter, TRUE);
2895
2896
0
    ORDER_MIN_MAX(m_XMinFilter, m_XMaxFilter);
2897
0
    ORDER_MIN_MAX(m_YMinFilter, m_YMaxFilter);
2898
0
    ORDER_MIN_MAX(m_sMinFilter.x, m_sMaxFilter.x);
2899
0
    ORDER_MIN_MAX(m_sMinFilter.y, m_sMaxFilter.y);
2900
0
}
2901
2902
/**********************************************************************
2903
 *                   TABMAPFile::ResetCoordFilter()
2904
 *
2905
 * Reset the MBR of the area of interest to be the extents as defined
2906
 * in the header.
2907
 **********************************************************************/
2908
2909
void TABMAPFile::ResetCoordFilter()
2910
2911
1.45k
{
2912
1.45k
    m_XMinFilter = m_poHeader->m_nXMin;
2913
1.45k
    m_YMinFilter = m_poHeader->m_nYMin;
2914
1.45k
    m_XMaxFilter = m_poHeader->m_nXMax;
2915
1.45k
    m_YMaxFilter = m_poHeader->m_nYMax;
2916
1.45k
    Int2Coordsys(m_XMinFilter, m_YMinFilter, m_sMinFilter.x, m_sMinFilter.y);
2917
1.45k
    Int2Coordsys(m_XMaxFilter, m_YMaxFilter, m_sMaxFilter.x, m_sMaxFilter.y);
2918
2919
1.45k
    ORDER_MIN_MAX(m_XMinFilter, m_XMaxFilter);
2920
1.45k
    ORDER_MIN_MAX(m_YMinFilter, m_YMaxFilter);
2921
1.45k
    ORDER_MIN_MAX(m_sMinFilter.x, m_sMaxFilter.x);
2922
1.45k
    ORDER_MIN_MAX(m_sMinFilter.y, m_sMaxFilter.y);
2923
1.45k
}
2924
2925
/**********************************************************************
2926
 *                   TABMAPFile::GetCoordFilter()
2927
 *
2928
 * Get the MBR of the area of interest, as previously set by
2929
 * SetCoordFilter().
2930
 *
2931
 * @param sMin vertex into which the minimum x/y values put in coordsys space.
2932
 * @param sMax vertex into which the maximum x/y values put in coordsys space.
2933
 **********************************************************************/
2934
void TABMAPFile::GetCoordFilter(TABVertex &sMin, TABVertex &sMax) const
2935
0
{
2936
0
    sMin = m_sMinFilter;
2937
0
    sMax = m_sMaxFilter;
2938
0
}
2939
2940
/**********************************************************************
2941
 *                   TABMAPFile::CommitSpatialIndex()
2942
 *
2943
 * Write the spatial index blocks tree for this file.
2944
 *
2945
 * This function applies only to write access mode.
2946
 *
2947
 * Returns 0 on success, -1 on error.
2948
 **********************************************************************/
2949
int TABMAPFile::CommitSpatialIndex()
2950
231
{
2951
231
    if (m_eAccessMode == TABRead || m_poHeader == nullptr)
2952
0
    {
2953
0
        CPLError(
2954
0
            CE_Failure, CPLE_AssertionFailed,
2955
0
            "CommitSpatialIndex() failed: file not opened for write access.");
2956
0
        return -1;
2957
0
    }
2958
2959
231
    if (m_poSpIndex == nullptr)
2960
67
    {
2961
67
        return 0;  // Nothing to do!
2962
67
    }
2963
2964
    /*-------------------------------------------------------------
2965
     * Update header fields and commit index block
2966
     * (its children will be recursively committed as well)
2967
     *------------------------------------------------------------*/
2968
    // Add 1 to Spatial Index Depth to account to the MapObjectBlocks
2969
164
    const int nNextDepth = m_poSpIndex->GetCurMaxDepth() + 1;
2970
164
    m_poHeader->m_nMaxSpIndexDepth = static_cast<GByte>(
2971
164
        std::max(static_cast<int>(m_poHeader->m_nMaxSpIndexDepth), nNextDepth));
2972
2973
164
    m_poSpIndex->GetMBR(m_poHeader->m_nXMin, m_poHeader->m_nYMin,
2974
164
                        m_poHeader->m_nXMax, m_poHeader->m_nYMax);
2975
2976
164
    return m_poSpIndex->CommitToFile();
2977
231
}
2978
2979
/**********************************************************************
2980
 *                   TABMAPFile::GetMinTABFileVersion()
2981
 *
2982
 * Returns the minimum TAB file version number that can contain all the
2983
 * objects stored in this file.
2984
 **********************************************************************/
2985
int TABMAPFile::GetMinTABFileVersion()
2986
231
{
2987
231
    int nToolVersion = 0;
2988
2989
231
    if (m_poToolDefTable)
2990
164
        nToolVersion = m_poToolDefTable->GetMinVersionNumber();
2991
2992
231
    return std::max(nToolVersion, m_nMinTABVersion);
2993
231
}
2994
2995
const CPLString &TABMAPFile::GetEncoding() const
2996
24
{
2997
24
    return m_osEncoding;
2998
24
}
2999
3000
void TABMAPFile::SetEncoding(const CPLString &osEncoding)
3001
0
{
3002
0
    m_osEncoding = osEncoding;
3003
0
}
3004
3005
bool TABMAPFile::IsValidObjType(int nObjType)
3006
1.29k
{
3007
1.29k
    switch (nObjType)
3008
1.29k
    {
3009
4
        case TAB_GEOM_NONE:
3010
7
        case TAB_GEOM_SYMBOL_C:
3011
133
        case TAB_GEOM_SYMBOL:
3012
133
        case TAB_GEOM_LINE_C:
3013
404
        case TAB_GEOM_LINE:
3014
406
        case TAB_GEOM_PLINE_C:
3015
406
        case TAB_GEOM_PLINE:
3016
406
        case TAB_GEOM_ARC_C:
3017
520
        case TAB_GEOM_ARC:
3018
580
        case TAB_GEOM_REGION_C:
3019
674
        case TAB_GEOM_REGION:
3020
674
        case TAB_GEOM_TEXT_C:
3021
724
        case TAB_GEOM_TEXT:
3022
724
        case TAB_GEOM_RECT_C:
3023
786
        case TAB_GEOM_RECT:
3024
786
        case TAB_GEOM_ROUNDRECT_C:
3025
852
        case TAB_GEOM_ROUNDRECT:
3026
854
        case TAB_GEOM_ELLIPSE_C:
3027
913
        case TAB_GEOM_ELLIPSE:
3028
980
        case TAB_GEOM_MULTIPLINE_C:
3029
980
        case TAB_GEOM_MULTIPLINE:
3030
980
        case TAB_GEOM_FONTSYMBOL_C:
3031
1.08k
        case TAB_GEOM_FONTSYMBOL:
3032
1.08k
        case TAB_GEOM_CUSTOMSYMBOL_C:
3033
1.18k
        case TAB_GEOM_CUSTOMSYMBOL:
3034
1.18k
        case TAB_GEOM_V450_REGION_C:
3035
1.18k
        case TAB_GEOM_V450_REGION:
3036
1.19k
        case TAB_GEOM_V450_MULTIPLINE_C:
3037
1.19k
        case TAB_GEOM_V450_MULTIPLINE:
3038
1.22k
        case TAB_GEOM_MULTIPOINT_C:
3039
1.22k
        case TAB_GEOM_MULTIPOINT:
3040
1.24k
        case TAB_GEOM_COLLECTION_C:
3041
1.27k
        case TAB_GEOM_COLLECTION:
3042
1.27k
        case TAB_GEOM_UNKNOWN1_C:
3043
1.27k
        case TAB_GEOM_UNKNOWN1:
3044
1.27k
        case TAB_GEOM_V800_REGION_C:
3045
1.27k
        case TAB_GEOM_V800_REGION:
3046
1.27k
        case TAB_GEOM_V800_MULTIPLINE_C:
3047
1.27k
        case TAB_GEOM_V800_MULTIPLINE:
3048
1.27k
        case TAB_GEOM_V800_MULTIPOINT_C:
3049
1.27k
        case TAB_GEOM_V800_MULTIPOINT:
3050
1.27k
        case TAB_GEOM_V800_COLLECTION_C:
3051
1.27k
        case TAB_GEOM_V800_COLLECTION:
3052
1.27k
            return true;
3053
3054
18
        default:
3055
18
            return false;
3056
1.29k
    }
3057
1.29k
}
3058
3059
/**********************************************************************
3060
 *                   TABMAPFile::Dump()
3061
 *
3062
 * Dump block contents... available only in DEBUG mode.
3063
 **********************************************************************/
3064
#ifdef DEBUG
3065
3066
void TABMAPFile::Dump(FILE *fpOut /*=NULL*/)
3067
{
3068
    if (fpOut == nullptr)
3069
        fpOut = stdout;
3070
3071
    fprintf(fpOut, "----- TABMAPFile::Dump() -----\n");
3072
3073
    if (m_fp == nullptr)
3074
    {
3075
        fprintf(fpOut, "File is not opened.\n");
3076
    }
3077
    else
3078
    {
3079
        fprintf(fpOut, "File is opened: %s\n", m_pszFname);
3080
        fprintf(fpOut, "Coordsys filter  = (%g,%g)-(%g,%g)\n", m_sMinFilter.x,
3081
                m_sMinFilter.y, m_sMaxFilter.x, m_sMaxFilter.y);
3082
        fprintf(fpOut, "Int coord filter = (%d,%d)-(%d,%d)\n", m_XMinFilter,
3083
                m_YMinFilter, m_XMaxFilter, m_YMaxFilter);
3084
3085
        fprintf(fpOut, "\nFile Header follows ...\n\n");
3086
        m_poHeader->Dump(fpOut);
3087
        fprintf(fpOut, "... end of file header.\n\n");
3088
3089
        fprintf(fpOut, "Associated .ID file ...\n\n");
3090
        m_poIdIndex->Dump(fpOut);
3091
        fprintf(fpOut, "... end of ID file dump.\n\n");
3092
    }
3093
3094
    fflush(fpOut);
3095
}
3096
3097
#endif  // DEBUG
3098
3099
/**********************************************************************
3100
 *                   TABMAPFile::DumpSpatialIndexToMIF()
3101
 *
3102
 * Dump the spatial index tree... available only in DEBUG mode.
3103
 **********************************************************************/
3104
#ifdef DEBUG
3105
3106
void TABMAPFile::DumpSpatialIndexToMIF(TABMAPIndexBlock *poNode, FILE *fpMIF,
3107
                                       FILE *fpMID, int nParentId /*=-1*/,
3108
                                       int nIndexInNode /*=-1*/,
3109
                                       int nCurDepth /*=0*/,
3110
                                       int nMaxDepth /*=-1*/)
3111
{
3112
    if (poNode == nullptr)
3113
    {
3114
        if (m_poHeader && m_poHeader->m_nFirstIndexBlock != 0)
3115
        {
3116
            TABRawBinBlock *poBlock =
3117
                GetIndexObjectBlock(m_poHeader->m_nFirstIndexBlock);
3118
            if (poBlock && poBlock->GetBlockType() == TABMAP_INDEX_BLOCK)
3119
                poNode = cpl::down_cast<TABMAPIndexBlock *>(poBlock);
3120
        }
3121
3122
        if (poNode == nullptr)
3123
            return;
3124
    }
3125
3126
    /*-------------------------------------------------------------
3127
     * Report info on current tree node
3128
     *------------------------------------------------------------*/
3129
    const int numEntries = poNode->GetNumEntries();
3130
    GInt32 nXMin = 0;
3131
    GInt32 nYMin = 0;
3132
    GInt32 nXMax = 0;
3133
    GInt32 nYMax = 0;
3134
3135
    poNode->RecomputeMBR();
3136
    poNode->GetMBR(nXMin, nYMin, nXMax, nYMax);
3137
3138
    double dXMin = 0.0;
3139
    double dYMin = 0.0;
3140
    double dXMax = 0.0;
3141
    double dYMax = 0.0;
3142
    Int2Coordsys(nXMin, nYMin, dXMin, dYMin);
3143
    Int2Coordsys(nXMax, nYMax, dXMax, dYMax);
3144
3145
    VSIFPrintf(fpMIF, "RECT %g %g %g %g\n", dXMin, dYMin, dXMax, dYMax);
3146
    VSIFPrintf(fpMIF, "  Brush(1, 0)\n"); /* No fill */
3147
3148
    VSIFPrintf(fpMID, "%d,%d,%d,%d,%g,%d,%d,%d,%d\n", poNode->GetStartAddress(),
3149
               nParentId, nIndexInNode, nCurDepth,
3150
               MITAB_AREA(nXMin, nYMin, nXMax, nYMax), nXMin, nYMin, nXMax,
3151
               nYMax);
3152
3153
    if (nMaxDepth != 0)
3154
    {
3155
        /*-------------------------------------------------------------
3156
         * Loop through all entries, dumping each of them
3157
         *------------------------------------------------------------*/
3158
        for (int i = 0; i < numEntries; i++)
3159
        {
3160
            TABMAPIndexEntry *psEntry = poNode->GetEntry(i);
3161
3162
            TABRawBinBlock *poBlock = GetIndexObjectBlock(psEntry->nBlockPtr);
3163
            if (poBlock == nullptr)
3164
                continue;
3165
3166
            if (poBlock->GetBlockType() == TABMAP_INDEX_BLOCK)
3167
            {
3168
                /* Index block, dump recursively */
3169
                DumpSpatialIndexToMIF(
3170
                    cpl::down_cast<TABMAPIndexBlock *>(poBlock), fpMIF, fpMID,
3171
                    poNode->GetStartAddress(), i, nCurDepth + 1, nMaxDepth - 1);
3172
            }
3173
            else
3174
            {
3175
                /* Object block, dump directly */
3176
                CPLAssert(poBlock->GetBlockType() == TABMAP_OBJECT_BLOCK);
3177
3178
                Int2Coordsys(psEntry->XMin, psEntry->YMin, dXMin, dYMin);
3179
                Int2Coordsys(psEntry->XMax, psEntry->YMax, dXMax, dYMax);
3180
3181
                VSIFPrintf(fpMIF, "RECT %g %g %g %g\n", dXMin, dYMin, dXMax,
3182
                           dYMax);
3183
                VSIFPrintf(fpMIF, "  Brush(1, 0)\n"); /* No fill */
3184
3185
                VSIFPrintf(
3186
                    fpMID, "%d,%d,%d,%d,%g,%d,%d,%d,%d\n", psEntry->nBlockPtr,
3187
                    poNode->GetStartAddress(), i, nCurDepth + 1,
3188
                    MITAB_AREA(psEntry->XMin, psEntry->YMin, psEntry->XMax,
3189
                               psEntry->YMax),
3190
                    psEntry->XMin, psEntry->YMin, psEntry->XMax, psEntry->YMax);
3191
            }
3192
3193
            delete poBlock;
3194
        }
3195
    }
3196
}
3197
3198
#endif  // DEBUG