Coverage Report

Created: 2025-07-23 09:13

/src/gdal/ogr/ogrsf_frmts/mitab/mitab_mapindexblock.cpp
Line
Count
Source (jump to first uncovered line)
1
/**********************************************************************
2
 *
3
 * Name:     mitab_mapindexblock.cpp
4
 * Project:  MapInfo TAB Read/Write library
5
 * Language: C++
6
 * Purpose:  Implementation of the TABMAPIndexBlock class used to handle
7
 *           reading/writing of the .MAP files' index blocks
8
 * Author:   Daniel Morissette, dmorissette@dmsolutions.ca
9
 *
10
 **********************************************************************
11
 * Copyright (c) 1999, 2000, 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 <cmath>
21
#include <cstdlib>
22
#include <cstring>
23
24
#include <algorithm>
25
26
#include "cpl_conv.h"
27
#include "cpl_error.h"
28
#include "cpl_vsi.h"
29
#include "mitab_priv.h"
30
31
/*=====================================================================
32
 *                      class TABMAPIndexBlock
33
 *====================================================================*/
34
35
/**********************************************************************
36
 *                   TABMAPIndexBlock::TABMAPIndexBlock()
37
 *
38
 * Constructor.
39
 **********************************************************************/
40
TABMAPIndexBlock::TABMAPIndexBlock(TABAccess eAccessMode /*= TABRead*/)
41
106
    : TABRawBinBlock(eAccessMode, TRUE), m_numEntries(0), m_nMinX(1000000000),
42
106
      m_nMinY(1000000000), m_nMaxX(-1000000000), m_nMaxY(-1000000000),
43
106
      m_poBlockManagerRef(nullptr), m_nCurChildIndex(-1), m_poParentRef(nullptr)
44
106
{
45
106
    memset(m_asEntries, 0, sizeof(m_asEntries));
46
106
}
47
48
/**********************************************************************
49
 *                   TABMAPIndexBlock::~TABMAPIndexBlock()
50
 *
51
 * Destructor.
52
 **********************************************************************/
53
TABMAPIndexBlock::~TABMAPIndexBlock()
54
106
{
55
106
    UnsetCurChild();
56
106
}
57
58
/**********************************************************************
59
 *                   TABMAPIndexBlock::UnsetCurChild()
60
 **********************************************************************/
61
62
void TABMAPIndexBlock::UnsetCurChild()
63
106
{
64
106
    if (m_poCurChild)
65
19
    {
66
19
        if (m_eAccess == TABWrite || m_eAccess == TABReadWrite)
67
19
            m_poCurChild->CommitToFile();
68
19
        m_poCurChild.reset();
69
19
    }
70
106
    m_nCurChildIndex = -1;
71
106
}
72
73
/**********************************************************************
74
 *                   TABMAPIndexBlock::InitBlockFromData()
75
 *
76
 * Perform some initialization on the block after its binary data has
77
 * been set or changed (or loaded from a file).
78
 *
79
 * Returns 0 if successful or -1 if an error happened, in which case
80
 * CPLError() will have been called.
81
 **********************************************************************/
82
int TABMAPIndexBlock::InitBlockFromData(GByte *pabyBuf, int nBlockSize,
83
                                        int nSizeUsed,
84
                                        GBool bMakeCopy /* = TRUE */,
85
                                        VSILFILE *fpSrc /* = NULL */,
86
                                        int nOffset /* = 0 */)
87
58
{
88
    /*-----------------------------------------------------------------
89
     * First of all, we must call the base class' InitBlockFromData()
90
     *----------------------------------------------------------------*/
91
58
    const int nStatus = TABRawBinBlock::InitBlockFromData(
92
58
        pabyBuf, nBlockSize, nSizeUsed, bMakeCopy, fpSrc, nOffset);
93
58
    if (nStatus != 0)
94
0
        return nStatus;
95
96
    /*-----------------------------------------------------------------
97
     * Validate block type
98
     *----------------------------------------------------------------*/
99
58
    if (m_nBlockType != TABMAP_INDEX_BLOCK)
100
0
    {
101
0
        CPLError(CE_Failure, CPLE_FileIO,
102
0
                 "InitBlockFromData(): Invalid Block Type: got %d expected %d",
103
0
                 m_nBlockType, TABMAP_INDEX_BLOCK);
104
0
        CPLFree(m_pabyBuf);
105
0
        m_pabyBuf = nullptr;
106
0
        return -1;
107
0
    }
108
109
    /*-----------------------------------------------------------------
110
     * Init member variables
111
     *----------------------------------------------------------------*/
112
58
    GotoByteInBlock(0x002);
113
58
    m_numEntries = ReadInt16();
114
115
58
    if (m_numEntries > 0)
116
58
        ReadAllEntries();
117
118
58
    return 0;
119
58
}
120
121
/**********************************************************************
122
 *                   TABMAPIndexBlock::CommitToFile()
123
 *
124
 * Commit the current state of the binary block to the file to which
125
 * it has been previously attached.
126
 *
127
 * This method makes sure all values are properly set in the map object
128
 * block header and then calls TABRawBinBlock::CommitToFile() to do
129
 * the actual writing to disk.
130
 *
131
 * Returns 0 if successful or -1 if an error happened, in which case
132
 * CPLError() will have been called.
133
 **********************************************************************/
134
int TABMAPIndexBlock::CommitToFile()
135
126
{
136
126
    if (m_pabyBuf == nullptr)
137
0
    {
138
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
139
0
                 "CommitToFile(): Block has not been initialized yet!");
140
0
        return -1;
141
0
    }
142
143
    /*-----------------------------------------------------------------
144
     * Commit child first
145
     *----------------------------------------------------------------*/
146
126
    if (m_poCurChild)
147
20
    {
148
20
        if (m_poCurChild->CommitToFile() != 0)
149
0
            return -1;
150
20
    }
151
152
    /*-----------------------------------------------------------------
153
     * Nothing to do here if block has not been modified
154
     *----------------------------------------------------------------*/
155
126
    if (!m_bModified)
156
20
        return 0;
157
158
    /*-----------------------------------------------------------------
159
     * Make sure 4 bytes block header is up to date.
160
     *----------------------------------------------------------------*/
161
106
    GotoByteInBlock(0x000);
162
163
106
    WriteInt16(TABMAP_INDEX_BLOCK);  // Block type code
164
106
    WriteInt16(static_cast<GInt16>(m_numEntries));
165
166
106
    int nStatus = CPLGetLastErrorType() == CE_Failure ? -1 : 0;
167
168
    /*-----------------------------------------------------------------
169
     * Loop through all entries, writing each of them, and calling
170
     * CommitToFile() (recursively) on any child index entries we may
171
     * encounter.
172
     *----------------------------------------------------------------*/
173
1.35k
    for (int i = 0; nStatus == 0 && i < m_numEntries; i++)
174
1.24k
    {
175
1.24k
        nStatus = WriteNextEntry(&(m_asEntries[i]));
176
1.24k
    }
177
178
    /*-----------------------------------------------------------------
179
     * OK, call the base class to write the block to disk.
180
     *----------------------------------------------------------------*/
181
106
    if (nStatus == 0)
182
106
    {
183
#ifdef DEBUG_VERBOSE
184
        CPLDebug("MITAB", "Committing INDEX block to offset %d", m_nFileOffset);
185
#endif
186
106
        nStatus = TABRawBinBlock::CommitToFile();
187
106
    }
188
189
106
    return nStatus;
190
126
}
191
192
/**********************************************************************
193
 *                   TABMAPIndexBlock::InitNewBlock()
194
 *
195
 * Initialize a newly created block so that it knows to which file it
196
 * is attached, its block size, etc . and then perform any specific
197
 * initialization for this block type, including writing a default
198
 * block header, etc. and leave the block ready to receive data.
199
 *
200
 * This is an alternative to calling ReadFromFile() or InitBlockFromData()
201
 * that puts the block in a stable state without loading any initial
202
 * data in it.
203
 *
204
 * Returns 0 if successful or -1 if an error happened, in which case
205
 * CPLError() will have been called.
206
 **********************************************************************/
207
int TABMAPIndexBlock::InitNewBlock(VSILFILE *fpSrc, int nBlockSize,
208
                                   int nFileOffset /* = 0*/)
209
48
{
210
    /*-----------------------------------------------------------------
211
     * Start with the default initialization
212
     *----------------------------------------------------------------*/
213
48
    if (TABRawBinBlock::InitNewBlock(fpSrc, nBlockSize, nFileOffset) != 0)
214
0
        return -1;
215
216
    /*-----------------------------------------------------------------
217
     * And then set default values for the block header.
218
     *----------------------------------------------------------------*/
219
48
    m_numEntries = 0;
220
221
48
    m_nMinX = 1000000000;
222
48
    m_nMinY = 1000000000;
223
48
    m_nMaxX = -1000000000;
224
48
    m_nMaxY = -1000000000;
225
226
48
    if (m_eAccess != TABRead && nFileOffset != 0)
227
48
    {
228
48
        GotoByteInBlock(0x000);
229
230
48
        WriteInt16(TABMAP_INDEX_BLOCK);  // Block type code
231
48
        WriteInt16(0);                   // num. index entries
232
48
    }
233
234
48
    if (CPLGetLastErrorType() == CE_Failure)
235
0
        return -1;
236
237
48
    return 0;
238
48
}
239
240
/**********************************************************************
241
 *                   TABMAPIndexBlock::ReadNextEntry()
242
 *
243
 * Read the next index entry from the block and fill the sEntry
244
 * structure.
245
 *
246
 * Returns 0 if successful or -1 if we reached the end of the block.
247
 **********************************************************************/
248
int TABMAPIndexBlock::ReadNextEntry(TABMAPIndexEntry *psEntry)
249
1.11k
{
250
1.11k
    if (m_nCurPos < 4)
251
0
        GotoByteInBlock(0x004);
252
253
1.11k
    if (m_nCurPos > 4 + (20 * m_numEntries))
254
0
    {
255
        // End of BLock
256
0
        return -1;
257
0
    }
258
259
1.11k
    psEntry->XMin = ReadInt32();
260
1.11k
    psEntry->YMin = ReadInt32();
261
1.11k
    psEntry->XMax = ReadInt32();
262
1.11k
    psEntry->YMax = ReadInt32();
263
1.11k
    psEntry->nBlockPtr = ReadInt32();
264
265
1.11k
    if (CPLGetLastErrorType() == CE_Failure)
266
0
        return -1;
267
268
1.11k
    return 0;
269
1.11k
}
270
271
/**********************************************************************
272
 *                   TABMAPIndexBlock::ReadAllEntries()
273
 *
274
 * Init the block by reading all entries from the data block.
275
 *
276
 * Returns 0 if successful or -1 on error.
277
 **********************************************************************/
278
int TABMAPIndexBlock::ReadAllEntries()
279
58
{
280
58
    CPLAssert(m_numEntries <= GetMaxEntries());
281
58
    if (m_numEntries == 0)
282
0
        return 0;
283
284
58
    if (GotoByteInBlock(0x004) != 0)
285
0
        return -1;
286
287
1.17k
    for (int i = 0; i < m_numEntries; i++)
288
1.11k
    {
289
1.11k
        if (ReadNextEntry(&(m_asEntries[i])) != 0)
290
0
            return -1;
291
1.11k
    }
292
293
58
    return 0;
294
58
}
295
296
/**********************************************************************
297
 *                   TABMAPIndexBlock::WriteNextEntry()
298
 *
299
 * Write the sEntry index entry at current position in the block.
300
 *
301
 * Returns 0 if successful or -1 if we reached the end of the block.
302
 **********************************************************************/
303
int TABMAPIndexBlock::WriteNextEntry(TABMAPIndexEntry *psEntry)
304
1.24k
{
305
1.24k
    if (m_nCurPos < 4)
306
0
        GotoByteInBlock(0x004);
307
308
1.24k
    WriteInt32(psEntry->XMin);
309
1.24k
    WriteInt32(psEntry->YMin);
310
1.24k
    WriteInt32(psEntry->XMax);
311
1.24k
    WriteInt32(psEntry->YMax);
312
1.24k
    WriteInt32(psEntry->nBlockPtr);
313
314
1.24k
    if (CPLGetLastErrorType() == CE_Failure)
315
0
        return -1;
316
317
1.24k
    return 0;
318
1.24k
}
319
320
/**********************************************************************
321
 *                   TABMAPIndexBlock::GetNumFreeEntries()
322
 *
323
 * Return the number of available entries in this block.
324
 *
325
 * __TODO__ This function could eventually be improved to search
326
 *          children leaves as well.
327
 **********************************************************************/
328
int TABMAPIndexBlock::GetNumFreeEntries()
329
1.22k
{
330
1.22k
    return (m_nBlockSize - 4) / 20 - m_numEntries;
331
1.22k
}
332
333
/**********************************************************************
334
 *                   TABMAPIndexBlock::GetEntry()
335
 *
336
 * Fetch a reference to the requested entry.
337
 *
338
 * @param iIndex index of entry, must be from 0 to GetNumEntries()-1.
339
 *
340
 * @return a reference to the internal copy of the entry, or NULL if out
341
 * of range.
342
 **********************************************************************/
343
TABMAPIndexEntry *TABMAPIndexBlock::GetEntry(int iIndex)
344
0
{
345
0
    if (iIndex < 0 || iIndex >= m_numEntries)
346
0
        return nullptr;
347
348
0
    return m_asEntries + iIndex;
349
0
}
350
351
/**********************************************************************
352
 *                   TABMAPIndexBlock::GetCurMaxDepth()
353
 *
354
 * Return maximum depth in the currently loaded part of the index tree
355
 **********************************************************************/
356
int TABMAPIndexBlock::GetCurMaxDepth()
357
158
{
358
158
    if (m_poCurChild)
359
62
        return m_poCurChild->GetCurMaxDepth() + 1;
360
361
96
    return 1; /* No current child... this node counts for one. */
362
158
}
363
364
/**********************************************************************
365
 *                   TABMAPIndexBlock::GetMBR()
366
 *
367
 * Return the MBR for the current block.
368
 **********************************************************************/
369
void TABMAPIndexBlock::GetMBR(GInt32 &nXMin, GInt32 &nYMin, GInt32 &nXMax,
370
                              GInt32 &nYMax)
371
2.09k
{
372
2.09k
    nXMin = m_nMinX;
373
2.09k
    nYMin = m_nMinY;
374
2.09k
    nXMax = m_nMaxX;
375
2.09k
    nYMax = m_nMaxY;
376
2.09k
}
377
378
/**********************************************************************
379
 *                   TABMAPIndexBlock::SetMBR()
380
 *
381
 **********************************************************************/
382
void TABMAPIndexBlock::SetMBR(GInt32 nXMin, GInt32 nYMin, GInt32 nXMax,
383
                              GInt32 nYMax)
384
0
{
385
0
    m_nMinX = nXMin;
386
0
    m_nMinY = nYMin;
387
0
    m_nMaxX = nXMax;
388
0
    m_nMaxY = nYMax;
389
0
}
390
391
/**********************************************************************
392
 *                   TABMAPIndexBlock::InsertEntry()
393
 *
394
 * Add a new entry to this index block.  It is assumed that there is at
395
 * least one free slot available, so if the block has to be split then it
396
 * should have been done prior to calling this function.
397
 *
398
 * Returns 0 on success, -1 on error.
399
 **********************************************************************/
400
int TABMAPIndexBlock::InsertEntry(GInt32 nXMin, GInt32 nYMin, GInt32 nXMax,
401
                                  GInt32 nYMax, GInt32 nBlockPtr)
402
1.10k
{
403
1.10k
    if (m_eAccess != TABWrite && m_eAccess != TABReadWrite)
404
0
    {
405
0
        CPLError(
406
0
            CE_Failure, CPLE_AssertionFailed,
407
0
            "Failed adding index entry: File not opened for write access.");
408
0
        return -1;
409
0
    }
410
411
1.10k
    if (GetNumFreeEntries() < 1)
412
0
    {
413
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
414
0
                 "Current Block Index is full, cannot add new entry.");
415
0
        return -1;
416
0
    }
417
418
    /*-----------------------------------------------------------------
419
     * Update count of entries and store new entry.
420
     *----------------------------------------------------------------*/
421
1.10k
    m_numEntries++;
422
1.10k
    CPLAssert(m_numEntries <= GetMaxEntries());
423
424
1.10k
    m_asEntries[m_numEntries - 1].XMin = nXMin;
425
1.10k
    m_asEntries[m_numEntries - 1].YMin = nYMin;
426
1.10k
    m_asEntries[m_numEntries - 1].XMax = nXMax;
427
1.10k
    m_asEntries[m_numEntries - 1].YMax = nYMax;
428
1.10k
    m_asEntries[m_numEntries - 1].nBlockPtr = nBlockPtr;
429
430
1.10k
    m_bModified = TRUE;
431
432
1.10k
    return 0;
433
1.10k
}
434
435
/**********************************************************************
436
 *                   TABMAPIndexBlock::ChooseSubEntryForInsert()
437
 *
438
 * Select the entry in this index block in which the new entry should
439
 * be inserted. The criteria used is to select the node whose MBR needs
440
 * the least enlargement to include the new entry. We resolve ties by
441
 * choosing the entry with the rectangle of smallest area.
442
 * (This is the ChooseSubtree part of Guttman's "ChooseLeaf" algorithm.)
443
 *
444
 * Returns the index of the best candidate or -1 of node is empty.
445
 **********************************************************************/
446
int TABMAPIndexBlock::ChooseSubEntryForInsert(GInt32 nXMin, GInt32 nYMin,
447
                                              GInt32 nXMax, GInt32 nYMax)
448
136
{
449
136
    GInt32 nBestCandidate = -1;
450
451
136
    double dOptimalAreaDiff = 0.0;
452
453
136
    const double dNewEntryArea = MITAB_AREA(nXMin, nYMin, nXMax, nYMax);
454
455
1.96k
    for (GInt32 i = 0; i < m_numEntries; i++)
456
1.82k
    {
457
1.82k
        double dAreaDiff = 0.0;
458
1.82k
        const double dAreaBefore =
459
1.82k
            MITAB_AREA(m_asEntries[i].XMin, m_asEntries[i].YMin,
460
1.82k
                       m_asEntries[i].XMax, m_asEntries[i].YMax);
461
462
        /* Does this entry fully contain the new entry's MBR ?
463
         */
464
1.82k
        const GBool bIsContained =
465
1.82k
            nXMin >= m_asEntries[i].XMin && nYMin >= m_asEntries[i].YMin &&
466
1.82k
            nXMax <= m_asEntries[i].XMax && nYMax <= m_asEntries[i].YMax;
467
468
1.82k
        if (bIsContained)
469
1.43k
        {
470
            /* If new entry is fully contained in this entry then
471
             * the area difference will be the difference between the area
472
             * of the entry to insert and the area of m_asEntries[i]
473
             *
474
             * The diff value is negative in this case.
475
             */
476
1.43k
            dAreaDiff = dNewEntryArea - dAreaBefore;
477
1.43k
        }
478
388
        else
479
388
        {
480
            /* Need to calculate the expanded MBR to calculate the area
481
             * difference.
482
             */
483
388
            GInt32 nXMin2 = std::min(m_asEntries[i].XMin, nXMin);
484
388
            GInt32 nYMin2 = std::min(m_asEntries[i].YMin, nYMin);
485
388
            GInt32 nXMax2 = std::max(m_asEntries[i].XMax, nXMax);
486
388
            GInt32 nYMax2 = std::max(m_asEntries[i].YMax, nYMax);
487
488
388
            dAreaDiff =
489
388
                MITAB_AREA(nXMin2, nYMin2, nXMax2, nYMax2) - dAreaBefore;
490
388
        }
491
492
        /* Is this a better candidate?
493
         * Note, possible Optimization: In case of tie, we could to pick the
494
         * candidate with the smallest area
495
         */
496
497
1.82k
        if (/* No best candidate yet */
498
1.82k
            (nBestCandidate == -1)
499
            /* or current candidate is contained and best candidate is not
500
               contained */
501
1.82k
            || (dAreaDiff < 0 && dOptimalAreaDiff >= 0)
502
            /* or if both are either contained or not contained then use the one
503
             * with the smallest area diff, which means maximum coverage in the
504
             * case of contained rects, or minimum area increase when not
505
             * contained
506
             */
507
1.82k
            || (((dOptimalAreaDiff < 0 && dAreaDiff < 0) ||
508
1.64k
                 (dOptimalAreaDiff > 0 && dAreaDiff > 0)) &&
509
1.64k
                std::abs(dAreaDiff) < std::abs(dOptimalAreaDiff)))
510
191
        {
511
191
            nBestCandidate = i;
512
191
            dOptimalAreaDiff = dAreaDiff;
513
191
        }
514
1.82k
    }
515
516
136
    return nBestCandidate;
517
136
}
518
519
/**********************************************************************
520
 *                   TABMAPIndexBlock::ChooseLeafForInsert()
521
 *
522
 * Recursively search the tree until we find the best leaf to
523
 * contain the specified object MBR.
524
 *
525
 * Returns the nBlockPtr of the selected leaf node entry (should be a
526
 * ref to a TABMAPObjectBlock) or -1 on error.
527
 *
528
 * After this call, m_poCurChild will be pointing at the selected child
529
 * node, for use by later calls to UpdateLeafEntry()
530
 **********************************************************************/
531
GInt32 TABMAPIndexBlock::ChooseLeafForInsert(GInt32 nXMin, GInt32 nYMin,
532
                                             GInt32 nXMax, GInt32 nYMax)
533
0
{
534
0
    GBool bFound = FALSE;
535
536
0
    if (m_numEntries < 0)
537
0
        return -1;
538
539
    /*-----------------------------------------------------------------
540
     * Look for the best candidate to contain the new entry
541
     *----------------------------------------------------------------*/
542
543
    // Make sure blocks currently in memory are written to disk.
544
    // TODO: Could we avoid deleting m_poCurChild if it is already
545
    //       the best candidate for insert?
546
0
    if (m_poCurChild)
547
0
    {
548
0
        m_poCurChild->CommitToFile();
549
0
        m_poCurChild.reset();
550
0
        m_nCurChildIndex = -1;
551
0
    }
552
553
0
    int nBestCandidate = ChooseSubEntryForInsert(nXMin, nYMin, nXMax, nYMax);
554
555
0
    CPLAssert(nBestCandidate != -1);
556
0
    if (nBestCandidate == -1)
557
0
        return -1; /* This should never happen! */
558
559
    // Try to load corresponding child... if it fails then we are
560
    // likely in a leaf node, so we'll add the new entry in the current
561
    // node.
562
563
    // Prevent error message if referred block not committed yet.
564
0
    CPLPushErrorHandler(CPLQuietErrorHandler);
565
566
0
    auto poBlock = std::unique_ptr<TABRawBinBlock>(
567
0
        TABCreateMAPBlockFromFile(m_fp, m_asEntries[nBestCandidate].nBlockPtr,
568
0
                                  m_nBlockSize, TRUE, TABReadWrite));
569
0
    if (poBlock != nullptr && poBlock->GetBlockClass() == TABMAP_INDEX_BLOCK)
570
0
    {
571
0
        m_poCurChild.reset(
572
0
            cpl::down_cast<TABMAPIndexBlock *>(poBlock.release()));
573
0
        m_nCurChildIndex = nBestCandidate;
574
0
        m_poCurChild->SetParentRef(this);
575
0
        m_poCurChild->SetMAPBlockManagerRef(m_poBlockManagerRef);
576
0
        bFound = TRUE;
577
0
    }
578
579
0
    CPLPopErrorHandler();
580
0
    CPLErrorReset();
581
582
0
    if (bFound)
583
0
    {
584
        /*-------------------------------------------------------------
585
         * Found a child leaf... pass the call to it.
586
         *------------------------------------------------------------*/
587
0
        return m_poCurChild->ChooseLeafForInsert(nXMin, nYMin, nXMax, nYMax);
588
0
    }
589
590
    /*-------------------------------------------------------------
591
     * Found no child index node... we must be at the leaf level
592
     * (leaf points at map object data blocks) so we return a ref
593
     * to the TABMAPObjBlock for insertion
594
     *------------------------------------------------------------*/
595
0
    return m_asEntries[nBestCandidate].nBlockPtr;
596
0
}
597
598
/**********************************************************************
599
 *                   TABMAPIndexBlock::GetCurLeafEntryMBR()
600
 *
601
 * Get the MBR for specified nBlockPtr in the leaf at the end of the
602
 * chain of m_poCurChild refs.
603
 *
604
 * This method requires that the chain of m_poCurChild refs already point
605
 * to a leaf that contains the specified nBlockPtr, it is usually called
606
 * right after ChooseLeafForInsert().
607
 *
608
 * Returns 0 on success, -1 on error.
609
 **********************************************************************/
610
int TABMAPIndexBlock::GetCurLeafEntryMBR(GInt32 nBlockPtr, GInt32 &nXMin,
611
                                         GInt32 &nYMin, GInt32 &nXMax,
612
                                         GInt32 &nYMax)
613
0
{
614
0
    if (m_poCurChild)
615
0
    {
616
        /* Pass the call down to current child */
617
0
        return m_poCurChild->GetCurLeafEntryMBR(nBlockPtr, nXMin, nYMin, nXMax,
618
0
                                                nYMax);
619
0
    }
620
621
    /* We're at the leaf level, look for the entry */
622
0
    for (int i = 0; i < m_numEntries; i++)
623
0
    {
624
0
        if (m_asEntries[i].nBlockPtr == nBlockPtr)
625
0
        {
626
            /* Found it. Return its MBR */
627
0
            nXMin = m_asEntries[i].XMin;
628
0
            nYMin = m_asEntries[i].YMin;
629
0
            nXMax = m_asEntries[i].XMax;
630
0
            nYMax = m_asEntries[i].YMax;
631
632
0
            return 0;
633
0
        }
634
0
    }
635
636
    /* Not found! This should not happen if method is used properly. */
637
0
    CPLError(CE_Failure, CPLE_AssertionFailed,
638
0
             "Entry to update not found in GetCurLeafEntryMBR()!");
639
0
    return -1;
640
0
}
641
642
/**********************************************************************
643
 *                   TABMAPIndexBlock::UpdateLeafEntry()
644
 *
645
 * Update the MBR for specified nBlockPtr in the leaf at the end of the
646
 * chain of m_poCurChild refs and update MBR of parents if required.
647
 *
648
 * This method requires that the chain of m_poCurChild refs already point
649
 * to a leaf that contains the specified nBlockPtr, it is usually called
650
 * right after ChooseLeafForInsert().
651
 *
652
 * Returns 0 on success, -1 on error.
653
 **********************************************************************/
654
int TABMAPIndexBlock::UpdateLeafEntry(GInt32 nBlockPtr, GInt32 nXMin,
655
                                      GInt32 nYMin, GInt32 nXMax, GInt32 nYMax)
656
0
{
657
0
    if (m_poCurChild)
658
0
    {
659
        /* Pass the call down to current child */
660
0
        return m_poCurChild->UpdateLeafEntry(nBlockPtr, nXMin, nYMin, nXMax,
661
0
                                             nYMax);
662
0
    }
663
664
    /* We're at the leaf level, look for the entry to update */
665
0
    for (int i = 0; i < m_numEntries; i++)
666
0
    {
667
0
        if (m_asEntries[i].nBlockPtr == nBlockPtr)
668
0
        {
669
            /* Found it. */
670
0
            TABMAPIndexEntry *psEntry = &m_asEntries[i];
671
672
0
            if (psEntry->XMin != nXMin || psEntry->YMin != nYMin ||
673
0
                psEntry->XMax != nXMax || psEntry->YMax != nYMax)
674
0
            {
675
                /* MBR changed. Update MBR of entry */
676
0
                psEntry->XMin = nXMin;
677
0
                psEntry->YMin = nYMin;
678
0
                psEntry->XMax = nXMax;
679
0
                psEntry->YMax = nYMax;
680
681
0
                m_bModified = TRUE;
682
683
                /* Update MBR of this node and all parents */
684
0
                RecomputeMBR();
685
0
            }
686
687
0
            return 0;
688
0
        }
689
0
    }
690
691
    /* Not found! This should not happen if method is used properly. */
692
0
    CPLError(CE_Failure, CPLE_AssertionFailed,
693
0
             "Entry to update not found in UpdateLeafEntry()!");
694
0
    return -1;
695
0
}
696
697
/**********************************************************************
698
 *                   TABMAPIndexBlock::AddEntry()
699
 *
700
 * Recursively search the tree until we encounter the best leaf to
701
 * contain the specified object MBR and add the new entry to it.
702
 *
703
 * In the even that the selected leaf node would be full, then it will be
704
 * split and this split can propagate up to its parent, etc.
705
 *
706
 * If bAddInThisNodeOnly=TRUE, then the entry is added only locally and
707
 * we do not try to update the child node.  This is used when the parent
708
 * of a node that is being split has to be updated.
709
 *
710
 * Returns 0 on success, -1 on error.
711
 **********************************************************************/
712
int TABMAPIndexBlock::AddEntry(GInt32 nXMin, GInt32 nYMin, GInt32 nXMax,
713
                               GInt32 nYMax, GInt32 nBlockPtr,
714
                               GBool bAddInThisNodeOnly /*=FALSE*/)
715
184
{
716
184
    GBool bFound = FALSE;
717
718
184
    if (m_eAccess != TABWrite && m_eAccess != TABReadWrite)
719
0
    {
720
0
        CPLError(
721
0
            CE_Failure, CPLE_AssertionFailed,
722
0
            "Failed adding index entry: File not opened for write access.");
723
0
        return -1;
724
0
    }
725
726
    /*-----------------------------------------------------------------
727
     * Look for the best candidate to contain the new entry
728
     *----------------------------------------------------------------*/
729
730
    /*-----------------------------------------------------------------
731
     * If bAddInThisNodeOnly=TRUE then we add the entry only locally
732
     * and do not need to look for the proper leaf to insert it.
733
     *----------------------------------------------------------------*/
734
184
    if (bAddInThisNodeOnly)
735
39
        bFound = TRUE;
736
737
184
    if (!bFound && m_numEntries > 0)
738
136
    {
739
        // Make sure blocks currently in memory are written to disk.
740
136
        if (m_poCurChild)
741
41
        {
742
41
            m_poCurChild->CommitToFile();
743
41
            m_poCurChild.reset();
744
41
            m_nCurChildIndex = -1;
745
41
        }
746
747
136
        int nBestCandidate =
748
136
            ChooseSubEntryForInsert(nXMin, nYMin, nXMax, nYMax);
749
750
136
        CPLAssert(nBestCandidate != -1);
751
752
136
        if (nBestCandidate != -1)
753
136
        {
754
            // Try to load corresponding child... if it fails then we are
755
            // likely in a leaf node, so we'll add the new entry in the current
756
            // node.
757
758
            // Prevent error message if referred block not committed yet.
759
136
            CPLPushErrorHandler(CPLQuietErrorHandler);
760
761
136
            auto poBlock =
762
136
                std::unique_ptr<TABRawBinBlock>(TABCreateMAPBlockFromFile(
763
136
                    m_fp, m_asEntries[nBestCandidate].nBlockPtr, m_nBlockSize,
764
136
                    TRUE, TABReadWrite));
765
136
            if (poBlock != nullptr &&
766
136
                poBlock->GetBlockClass() == TABMAP_INDEX_BLOCK)
767
58
            {
768
58
                m_poCurChild.reset(
769
58
                    cpl::down_cast<TABMAPIndexBlock *>(poBlock.release()));
770
58
                m_nCurChildIndex = nBestCandidate;
771
58
                m_poCurChild->SetParentRef(this);
772
58
                m_poCurChild->SetMAPBlockManagerRef(m_poBlockManagerRef);
773
58
                bFound = TRUE;
774
58
            }
775
776
136
            CPLPopErrorHandler();
777
136
            CPLErrorReset();
778
136
        }
779
136
    }
780
781
184
    if (bFound && !bAddInThisNodeOnly)
782
58
    {
783
        /*-------------------------------------------------------------
784
         * Found a child leaf... pass the call to it.
785
         *------------------------------------------------------------*/
786
58
        if (m_poCurChild->AddEntry(nXMin, nYMin, nXMax, nYMax, nBlockPtr) != 0)
787
0
            return -1;
788
58
    }
789
126
    else
790
126
    {
791
        /*-------------------------------------------------------------
792
         * Found no child to store new object... we're likely at the leaf
793
         * level so we'll store new object in current node
794
         *------------------------------------------------------------*/
795
796
        /*-------------------------------------------------------------
797
         * First thing to do is make sure that there is room for a new
798
         * entry in this node, and to split it if necessary.
799
         *------------------------------------------------------------*/
800
126
        if (GetNumFreeEntries() < 1)
801
37
        {
802
37
            if (m_poParentRef == nullptr)
803
2
            {
804
                /*-----------------------------------------------------
805
                 * Splitting the root node adds one level to the tree, so
806
                 * after splitting we just redirect the call to the new
807
                 * child that's just been created.
808
                 *----------------------------------------------------*/
809
2
                if (SplitRootNode(nXMin, nYMin, nXMax, nYMax) != 0)
810
0
                    return -1;  // Error happened and has already been reported
811
812
2
                CPLAssert(m_poCurChild);
813
2
                return m_poCurChild->AddEntry(nXMin, nYMin, nXMax, nYMax,
814
2
                                              nBlockPtr, TRUE);
815
2
            }
816
35
            else
817
35
            {
818
                /*-----------------------------------------------------
819
                 * Splitting a regular node
820
                 *----------------------------------------------------*/
821
35
                if (SplitNode(nXMin, nYMin, nXMax, nYMax) != 0)
822
0
                    return -1;
823
35
            }
824
37
        }
825
826
124
        if (InsertEntry(nXMin, nYMin, nXMax, nYMax, nBlockPtr) != 0)
827
0
            return -1;
828
124
    }
829
830
    /*-----------------------------------------------------------------
831
     * Update current node MBR and the reference to it in our parent.
832
     *----------------------------------------------------------------*/
833
182
    RecomputeMBR();
834
835
182
    return 0;
836
184
}
837
838
/**********************************************************************
839
 *                   TABMAPIndexBlock::ComputeAreaDiff()
840
 *
841
 * (static method, also used by the TABMAPObjBlock class)
842
 *
843
 * Compute the area difference between two MBRs. Used in the SplitNode
844
 * algorithm to decide to which of the two nodes an entry should be added.
845
 *
846
 * The returned AreaDiff value is positive if NodeMBR has to be enlarged
847
 * and negative if new Entry is fully contained in the NodeMBR.
848
 **********************************************************************/
849
double TABMAPIndexBlock::ComputeAreaDiff(GInt32 nNodeXMin, GInt32 nNodeYMin,
850
                                         GInt32 nNodeXMax, GInt32 nNodeYMax,
851
                                         GInt32 nEntryXMin, GInt32 nEntryYMin,
852
                                         GInt32 nEntryXMax, GInt32 nEntryYMax)
853
1.77k
{
854
1.77k
    double dAreaDiff = 0.0;
855
856
1.77k
    const double dNodeAreaBefore =
857
1.77k
        MITAB_AREA(nNodeXMin, nNodeYMin, nNodeXMax, nNodeYMax);
858
859
    // Does the node fully contain the new entry's MBR?
860
1.77k
    const GBool bIsContained =
861
1.77k
        nEntryXMin >= nNodeXMin && nEntryYMin >= nNodeYMin &&
862
1.77k
        nEntryXMax <= nNodeXMax && nEntryYMax <= nNodeYMax;
863
864
1.77k
    if (bIsContained)
865
1.60k
    {
866
        /* If new entry is fully contained in this entry then
867
         * the area difference will be the difference between the area
868
         * of the entry to insert and the area of the node
869
         */
870
1.60k
        dAreaDiff = MITAB_AREA(nEntryXMin, nEntryYMin, nEntryXMax, nEntryYMax) -
871
1.60k
                    dNodeAreaBefore;
872
1.60k
    }
873
172
    else
874
172
    {
875
        /* Need to calculate the expanded MBR to calculate the area
876
         * difference.
877
         */
878
172
        nNodeXMin = std::min(nNodeXMin, nEntryXMin);
879
172
        nNodeYMin = std::min(nNodeYMin, nEntryYMin);
880
172
        nNodeXMax = std::max(nNodeXMax, nEntryXMax);
881
172
        nNodeYMax = std::max(nNodeYMax, nEntryYMax);
882
883
172
        dAreaDiff = MITAB_AREA(nNodeXMin, nNodeYMin, nNodeXMax, nNodeYMax) -
884
172
                    dNodeAreaBefore;
885
172
    }
886
887
1.77k
    return dAreaDiff;
888
1.77k
}
889
890
/**********************************************************************
891
 *                   TABMAPIndexBlock::PickSeedsForSplit()
892
 *
893
 * (static method, also used by the TABMAPObjBlock class)
894
 *
895
 * Pick two seeds to use to start splitting this node.
896
 *
897
 * Guttman's LinearPickSeed:
898
 * - Along each dimension find the entry whose rectangle has the
899
 *   highest low side, and the one with the lowest high side
900
 * - Calculate the separation for each pair
901
 * - Normalize the separation by dividing by the extents of the
902
 *   corresponding dimension
903
 * - Choose the pair with the greatest normalized separation along
904
 *   any dimension
905
 **********************************************************************/
906
int TABMAPIndexBlock::PickSeedsForSplit(
907
    TABMAPIndexEntry *pasEntries, int numEntries, int nSrcCurChildIndex,
908
    GInt32 nNewEntryXMin, GInt32 nNewEntryYMin, GInt32 nNewEntryXMax,
909
    GInt32 nNewEntryYMax, int &nSeed1, int &nSeed2)
910
37
{
911
37
    GInt32 nSrcMinX = 0;
912
37
    GInt32 nSrcMinY = 0;
913
37
    GInt32 nSrcMaxX = 0;
914
37
    GInt32 nSrcMaxY = 0;
915
37
    int nLowestMaxX = -1;
916
37
    int nHighestMinX = -1;
917
37
    int nLowestMaxY = -1;
918
37
    int nHighestMinY = -1;
919
37
    GInt32 nLowestMaxXId = -1;
920
37
    GInt32 nHighestMinXId = -1;
921
37
    GInt32 nLowestMaxYId = -1;
922
37
    GInt32 nHighestMinYId = -1;
923
924
37
    nSeed1 = -1;
925
37
    nSeed2 = -1;
926
927
    // Along each dimension find the entry whose rectangle has the
928
    // highest low side, and the one with the lowest high side
929
962
    for (int iEntry = 0; iEntry < numEntries; iEntry++)
930
925
    {
931
925
        if (nLowestMaxXId == -1 || pasEntries[iEntry].XMax < nLowestMaxX)
932
71
        {
933
71
            nLowestMaxX = pasEntries[iEntry].XMax;
934
71
            nLowestMaxXId = iEntry;
935
71
        }
936
937
925
        if (nHighestMinXId == -1 || pasEntries[iEntry].XMin > nHighestMinX)
938
37
        {
939
37
            nHighestMinX = pasEntries[iEntry].XMin;
940
37
            nHighestMinXId = iEntry;
941
37
        }
942
943
925
        if (nLowestMaxYId == -1 || pasEntries[iEntry].YMax < nLowestMaxY)
944
71
        {
945
71
            nLowestMaxY = pasEntries[iEntry].YMax;
946
71
            nLowestMaxYId = iEntry;
947
71
        }
948
949
925
        if (nHighestMinYId == -1 || pasEntries[iEntry].YMin > nHighestMinY)
950
38
        {
951
38
            nHighestMinY = pasEntries[iEntry].YMin;
952
38
            nHighestMinYId = iEntry;
953
38
        }
954
955
        // Also keep track of MBR of all entries
956
925
        if (iEntry == 0)
957
37
        {
958
37
            nSrcMinX = pasEntries[iEntry].XMin;
959
37
            nSrcMinY = pasEntries[iEntry].YMin;
960
37
            nSrcMaxX = pasEntries[iEntry].XMax;
961
37
            nSrcMaxY = pasEntries[iEntry].YMax;
962
37
        }
963
888
        else
964
888
        {
965
888
            nSrcMinX = std::min(nSrcMinX, pasEntries[iEntry].XMin);
966
888
            nSrcMinY = std::min(nSrcMinY, pasEntries[iEntry].YMin);
967
888
            nSrcMaxX = std::max(nSrcMaxX, pasEntries[iEntry].XMax);
968
888
            nSrcMaxY = std::max(nSrcMaxY, pasEntries[iEntry].YMax);
969
888
        }
970
925
    }
971
972
37
    const double dfSrcWidth =
973
37
        std::abs(static_cast<double>(nSrcMaxX) - nSrcMinX);
974
37
    const double dfSrcHeight =
975
37
        std::abs(static_cast<double>(nSrcMaxY) - nSrcMinY);
976
977
    // Calculate the separation for each pair (note that it may be negative
978
    // in case of overlap)
979
    // Normalize the separation by dividing by the extents of the
980
    // corresponding dimension
981
37
    const double dX =
982
37
        dfSrcWidth == 0.0
983
37
            ? 0.0
984
37
            : (static_cast<double>(nHighestMinX) - nLowestMaxX) / dfSrcWidth;
985
37
    const double dY =
986
37
        dfSrcHeight == 0.0
987
37
            ? 0.0
988
37
            : (static_cast<double>(nHighestMinY) - nLowestMaxY) / dfSrcHeight;
989
990
    // Choose the pair with the greatest normalized separation along
991
    // any dimension
992
37
    if (dX > dY)
993
0
    {
994
0
        nSeed1 = nHighestMinXId;
995
0
        nSeed2 = nLowestMaxXId;
996
0
    }
997
37
    else
998
37
    {
999
37
        nSeed1 = nHighestMinYId;
1000
37
        nSeed2 = nLowestMaxYId;
1001
37
    }
1002
1003
    // If nSeed1==nSeed2 then just pick any two (giving pref to current child)
1004
37
    if (nSeed1 == nSeed2)
1005
2
    {
1006
2
        if (nSeed1 != nSrcCurChildIndex && nSrcCurChildIndex != -1)
1007
0
            nSeed1 = nSrcCurChildIndex;
1008
2
        else if (nSeed1 != 0)
1009
0
            nSeed1 = 0;
1010
2
        else
1011
2
            nSeed1 = 1;
1012
2
    }
1013
1014
    // Decide which of the two seeds best matches the new entry. That seed and
1015
    // the new entry will stay in current node (new entry will be added by the
1016
    // caller later). The other seed will go in the 2nd node
1017
37
    const double dAreaDiff1 = ComputeAreaDiff(
1018
37
        pasEntries[nSeed1].XMin, pasEntries[nSeed1].YMin,
1019
37
        pasEntries[nSeed1].XMax, pasEntries[nSeed1].YMax, nNewEntryXMin,
1020
37
        nNewEntryYMin, nNewEntryXMax, nNewEntryYMax);
1021
1022
37
    const double dAreaDiff2 = ComputeAreaDiff(
1023
37
        pasEntries[nSeed2].XMin, pasEntries[nSeed2].YMin,
1024
37
        pasEntries[nSeed2].XMax, pasEntries[nSeed2].YMax, nNewEntryXMin,
1025
37
        nNewEntryYMin, nNewEntryXMax, nNewEntryYMax);
1026
1027
    /* Note that we want to keep this node's current child in here.
1028
     * Since splitting happens only during an addentry() operation and
1029
     * then both the current child and the New Entry should fit in the same
1030
     * area.
1031
     */
1032
37
    if (nSeed1 != nSrcCurChildIndex &&
1033
37
        (dAreaDiff1 > dAreaDiff2 || nSeed2 == nSrcCurChildIndex))
1034
3
    {
1035
        // Seed2 stays in this node, Seed1 moves to new node
1036
        // ... swap Seed1 and Seed2 indices
1037
3
        int nTmp = nSeed1;
1038
3
        nSeed1 = nSeed2;
1039
3
        nSeed2 = nTmp;
1040
3
    }
1041
1042
37
    return 0;
1043
37
}
1044
1045
/**********************************************************************
1046
 *                   TABMAPIndexBlock::SplitNode()
1047
 *
1048
 * Split current Node, update the references in the parent node, etc.
1049
 * Note that Root Nodes cannot be split using this method... SplitRootNode()
1050
 * should be used instead.
1051
 *
1052
 * nNewEntry* are the coord. of the new entry that
1053
 * will be added after the split.  The split is done so that the current
1054
 * node will be the one in which the new object should be stored.
1055
 *
1056
 * Returns 0 on success, -1 on error.
1057
 **********************************************************************/
1058
int TABMAPIndexBlock::SplitNode(GInt32 nNewEntryXMin, GInt32 nNewEntryYMin,
1059
                                GInt32 nNewEntryXMax, GInt32 nNewEntryYMax)
1060
37
{
1061
37
    CPLAssert(m_poBlockManagerRef);
1062
1063
    /*-----------------------------------------------------------------
1064
     * Create a 2nd node
1065
     *----------------------------------------------------------------*/
1066
37
    TABMAPIndexBlock *poNewNode = new TABMAPIndexBlock(m_eAccess);
1067
37
    if (poNewNode->InitNewBlock(m_fp, m_nBlockSize,
1068
37
                                m_poBlockManagerRef->AllocNewBlock("INDEX")) !=
1069
37
        0)
1070
0
    {
1071
0
        return -1;
1072
0
    }
1073
37
    poNewNode->SetMAPBlockManagerRef(m_poBlockManagerRef);
1074
1075
    /*-----------------------------------------------------------------
1076
     * Make a temporary copy of the entries in current node
1077
     *----------------------------------------------------------------*/
1078
37
    int nSrcEntries = m_numEntries;
1079
37
    TABMAPIndexEntry *pasSrcEntries = static_cast<TABMAPIndexEntry *>(
1080
37
        CPLMalloc(m_numEntries * sizeof(TABMAPIndexEntry)));
1081
37
    memcpy(pasSrcEntries, &m_asEntries,
1082
37
           m_numEntries * sizeof(TABMAPIndexEntry));
1083
1084
37
    int nSrcCurChildIndex = m_nCurChildIndex;
1085
1086
    /*-----------------------------------------------------------------
1087
     * Pick Seeds for each node
1088
     *----------------------------------------------------------------*/
1089
37
    int nSeed1, nSeed2;
1090
37
    PickSeedsForSplit(pasSrcEntries, nSrcEntries, nSrcCurChildIndex,
1091
37
                      nNewEntryXMin, nNewEntryYMin, nNewEntryXMax,
1092
37
                      nNewEntryYMax, nSeed1, nSeed2);
1093
1094
    /*-----------------------------------------------------------------
1095
     * Reset number of entries in this node and start moving new entries
1096
     *----------------------------------------------------------------*/
1097
37
    m_numEntries = 0;
1098
1099
    // Insert nSeed1 in this node
1100
37
    InsertEntry(pasSrcEntries[nSeed1].XMin, pasSrcEntries[nSeed1].YMin,
1101
37
                pasSrcEntries[nSeed1].XMax, pasSrcEntries[nSeed1].YMax,
1102
37
                pasSrcEntries[nSeed1].nBlockPtr);
1103
1104
    // Move nSeed2 to 2nd node
1105
37
    poNewNode->InsertEntry(
1106
37
        pasSrcEntries[nSeed2].XMin, pasSrcEntries[nSeed2].YMin,
1107
37
        pasSrcEntries[nSeed2].XMax, pasSrcEntries[nSeed2].YMax,
1108
37
        pasSrcEntries[nSeed2].nBlockPtr);
1109
1110
    // Update cur child index if necessary
1111
37
    if (nSeed1 == nSrcCurChildIndex)
1112
1
        m_nCurChildIndex = m_numEntries - 1;
1113
1114
    /*-----------------------------------------------------------------
1115
     * Go through the rest of the entries and assign them to one
1116
     * of the 2 nodes.
1117
     *
1118
     * Criteria is minimal area difference.
1119
     * Resolve ties by adding the entry to the node with smaller total
1120
     * area, then to the one with fewer entries, then to either.
1121
     *----------------------------------------------------------------*/
1122
962
    for (int iEntry = 0; iEntry < nSrcEntries; iEntry++)
1123
925
    {
1124
925
        if (iEntry == nSeed1 || iEntry == nSeed2)
1125
74
            continue;
1126
1127
        // If one of the two nodes is almost full then all remaining
1128
        // entries should go to the other node
1129
        // The entry corresponding to the current child also automatically
1130
        // stays in this node.
1131
851
        if (iEntry == nSrcCurChildIndex)
1132
0
        {
1133
0
            InsertEntry(pasSrcEntries[iEntry].XMin, pasSrcEntries[iEntry].YMin,
1134
0
                        pasSrcEntries[iEntry].XMax, pasSrcEntries[iEntry].YMax,
1135
0
                        pasSrcEntries[iEntry].nBlockPtr);
1136
1137
            // Update current child index
1138
0
            m_nCurChildIndex = m_numEntries - 1;
1139
1140
0
            continue;
1141
0
        }
1142
851
        else if (m_numEntries >= GetMaxEntries() - 1)
1143
0
        {
1144
0
            poNewNode->InsertEntry(
1145
0
                pasSrcEntries[iEntry].XMin, pasSrcEntries[iEntry].YMin,
1146
0
                pasSrcEntries[iEntry].XMax, pasSrcEntries[iEntry].YMax,
1147
0
                pasSrcEntries[iEntry].nBlockPtr);
1148
0
            continue;
1149
0
        }
1150
851
        else if (poNewNode->GetNumEntries() >= GetMaxEntries() - 1)
1151
0
        {
1152
0
            InsertEntry(pasSrcEntries[iEntry].XMin, pasSrcEntries[iEntry].YMin,
1153
0
                        pasSrcEntries[iEntry].XMax, pasSrcEntries[iEntry].YMax,
1154
0
                        pasSrcEntries[iEntry].nBlockPtr);
1155
0
            continue;
1156
0
        }
1157
1158
        // Decide which of the two nodes to put this entry in
1159
851
        RecomputeMBR();
1160
851
        const double dAreaDiff1 = ComputeAreaDiff(
1161
851
            m_nMinX, m_nMinY, m_nMaxX, m_nMaxY, pasSrcEntries[iEntry].XMin,
1162
851
            pasSrcEntries[iEntry].YMin, pasSrcEntries[iEntry].XMax,
1163
851
            pasSrcEntries[iEntry].YMax);
1164
1165
851
        GInt32 nXMin2 = 0;
1166
851
        GInt32 nYMin2 = 0;
1167
851
        GInt32 nXMax2 = 0;
1168
851
        GInt32 nYMax2 = 0;
1169
851
        poNewNode->RecomputeMBR();
1170
851
        poNewNode->GetMBR(nXMin2, nYMin2, nXMax2, nYMax2);
1171
851
        const double dAreaDiff2 = ComputeAreaDiff(
1172
851
            nXMin2, nYMin2, nXMax2, nYMax2, pasSrcEntries[iEntry].XMin,
1173
851
            pasSrcEntries[iEntry].YMin, pasSrcEntries[iEntry].XMax,
1174
851
            pasSrcEntries[iEntry].YMax);
1175
851
        if (dAreaDiff1 < dAreaDiff2)
1176
782
        {
1177
            // This entry stays in this node.
1178
782
            InsertEntry(pasSrcEntries[iEntry].XMin, pasSrcEntries[iEntry].YMin,
1179
782
                        pasSrcEntries[iEntry].XMax, pasSrcEntries[iEntry].YMax,
1180
782
                        pasSrcEntries[iEntry].nBlockPtr);
1181
782
        }
1182
69
        else
1183
69
        {
1184
            // This entry goes to new node
1185
69
            poNewNode->InsertEntry(
1186
69
                pasSrcEntries[iEntry].XMin, pasSrcEntries[iEntry].YMin,
1187
69
                pasSrcEntries[iEntry].XMax, pasSrcEntries[iEntry].YMax,
1188
69
                pasSrcEntries[iEntry].nBlockPtr);
1189
69
        }
1190
851
    }
1191
1192
    /*-----------------------------------------------------------------
1193
     * Recompute MBR and update current node info in parent
1194
     *----------------------------------------------------------------*/
1195
37
    RecomputeMBR();
1196
37
    poNewNode->RecomputeMBR();
1197
1198
    /*-----------------------------------------------------------------
1199
     * Add second node info to parent and then flush it to disk.
1200
     * This may trigger splitting of parent
1201
     *----------------------------------------------------------------*/
1202
37
    CPLAssert(m_poParentRef);
1203
37
    int nMinX, nMinY, nMaxX, nMaxY;
1204
37
    poNewNode->GetMBR(nMinX, nMinY, nMaxX, nMaxY);
1205
37
    m_poParentRef->AddEntry(nMinX, nMinY, nMaxX, nMaxY,
1206
37
                            poNewNode->GetNodeBlockPtr(), TRUE);
1207
37
    poNewNode->CommitToFile();
1208
37
    delete poNewNode;
1209
1210
37
    CPLFree(pasSrcEntries);
1211
1212
37
    return 0;
1213
37
}
1214
1215
/**********************************************************************
1216
 *                   TABMAPIndexBlock::SplitRootNode()
1217
 *
1218
 * (private method)
1219
 *
1220
 * Split a Root Node.
1221
 * First, a level of nodes must be added to the tree, then the contents
1222
 * of what used to be the root node is moved 1 level down and then that
1223
 * node is split like a regular node.
1224
 *
1225
 * Returns 0 on success, -1 on error
1226
 **********************************************************************/
1227
int TABMAPIndexBlock::SplitRootNode(GInt32 nNewEntryXMin, GInt32 nNewEntryYMin,
1228
                                    GInt32 nNewEntryXMax, GInt32 nNewEntryYMax)
1229
2
{
1230
2
    CPLAssert(m_poBlockManagerRef);
1231
2
    CPLAssert(m_poParentRef == nullptr);
1232
1233
    /*-----------------------------------------------------------------
1234
     * Since a root note cannot be split, we add a level of nodes
1235
     * under it and we'll do the split at that level.
1236
     *----------------------------------------------------------------*/
1237
2
    auto poNewNode = std::make_unique<TABMAPIndexBlock>(m_eAccess);
1238
1239
2
    if (poNewNode->InitNewBlock(m_fp, m_nBlockSize,
1240
2
                                m_poBlockManagerRef->AllocNewBlock("INDEX")) !=
1241
2
        0)
1242
0
    {
1243
0
        return -1;
1244
0
    }
1245
2
    poNewNode->SetMAPBlockManagerRef(m_poBlockManagerRef);
1246
1247
    // Move all entries to the new child
1248
2
    int nSrcEntries = m_numEntries;
1249
2
    m_numEntries = 0;
1250
52
    for (int iEntry = 0; iEntry < nSrcEntries; iEntry++)
1251
50
    {
1252
50
        poNewNode->InsertEntry(
1253
50
            m_asEntries[iEntry].XMin, m_asEntries[iEntry].YMin,
1254
50
            m_asEntries[iEntry].XMax, m_asEntries[iEntry].YMax,
1255
50
            m_asEntries[iEntry].nBlockPtr);
1256
50
    }
1257
1258
    /*-----------------------------------------------------------------
1259
     * Transfer current child object to new node.
1260
     *----------------------------------------------------------------*/
1261
2
    if (m_poCurChild)
1262
1
    {
1263
1
        poNewNode->SetCurChild(std::move(m_poCurChild), m_nCurChildIndex);
1264
1
        m_nCurChildIndex = -1;
1265
1
    }
1266
1267
    /*-----------------------------------------------------------------
1268
     * Place info about new child in current node.
1269
     *----------------------------------------------------------------*/
1270
2
    poNewNode->RecomputeMBR();
1271
2
    int nMinX, nMinY, nMaxX, nMaxY;
1272
2
    poNewNode->GetMBR(nMinX, nMinY, nMaxX, nMaxY);
1273
2
    InsertEntry(nMinX, nMinY, nMaxX, nMaxY, poNewNode->GetNodeBlockPtr());
1274
1275
    /*-----------------------------------------------------------------
1276
     * Keep a reference to the new child
1277
     *----------------------------------------------------------------*/
1278
2
    poNewNode->SetParentRef(this);
1279
2
    m_poCurChild = std::move(poNewNode);
1280
2
    m_nCurChildIndex = m_numEntries - 1;
1281
1282
    /*-----------------------------------------------------------------
1283
     * And finally force the child to split itself
1284
     *----------------------------------------------------------------*/
1285
2
    return m_poCurChild->SplitNode(nNewEntryXMin, nNewEntryYMin, nNewEntryXMax,
1286
2
                                   nNewEntryYMax);
1287
2
}
1288
1289
/**********************************************************************
1290
 *                   TABMAPIndexBlock::RecomputeMBR()
1291
 *
1292
 * Recompute current block MBR, and update info in parent.
1293
 **********************************************************************/
1294
void TABMAPIndexBlock::RecomputeMBR()
1295
1.96k
{
1296
1.96k
    GInt32 nMinX, nMinY, nMaxX, nMaxY;
1297
1298
1.96k
    nMinX = 1000000000;
1299
1.96k
    nMinY = 1000000000;
1300
1.96k
    nMaxX = -1000000000;
1301
1.96k
    nMaxY = -1000000000;
1302
1303
16.2k
    for (int i = 0; i < m_numEntries; i++)
1304
14.2k
    {
1305
14.2k
        if (m_asEntries[i].XMin < nMinX)
1306
1.97k
            nMinX = m_asEntries[i].XMin;
1307
14.2k
        if (m_asEntries[i].XMax > nMaxX)
1308
2.03k
            nMaxX = m_asEntries[i].XMax;
1309
1310
14.2k
        if (m_asEntries[i].YMin < nMinY)
1311
2.90k
            nMinY = m_asEntries[i].YMin;
1312
14.2k
        if (m_asEntries[i].YMax > nMaxY)
1313
2.03k
            nMaxY = m_asEntries[i].YMax;
1314
14.2k
    }
1315
1316
1.96k
    if (m_nMinX != nMinX || m_nMinY != nMinY || m_nMaxX != nMaxX ||
1317
1.96k
        m_nMaxY != nMaxY)
1318
151
    {
1319
151
        m_nMinX = nMinX;
1320
151
        m_nMinY = nMinY;
1321
151
        m_nMaxX = nMaxX;
1322
151
        m_nMaxY = nMaxY;
1323
1324
151
        m_bModified = TRUE;
1325
1326
151
        if (m_poParentRef)
1327
96
            m_poParentRef->UpdateCurChildMBR(m_nMinX, m_nMinY, m_nMaxX, m_nMaxY,
1328
96
                                             GetNodeBlockPtr());
1329
151
    }
1330
1.96k
}
1331
1332
/**********************************************************************
1333
 *                   TABMAPIndexBlock::UpdateCurChildMBR()
1334
 *
1335
 * Update current child MBR info, and propagate info in parent.
1336
 *
1337
 * nBlockPtr is passed only to validate the consistency of the tree.
1338
 **********************************************************************/
1339
void TABMAPIndexBlock::UpdateCurChildMBR(GInt32 nXMin, GInt32 nYMin,
1340
                                         GInt32 nXMax, GInt32 nYMax,
1341
                                         CPL_UNUSED GInt32 nBlockPtr)
1342
129
{
1343
129
    CPLAssert(m_poCurChild);
1344
129
    CPLAssert(m_asEntries[m_nCurChildIndex].nBlockPtr == nBlockPtr);
1345
1346
129
    if (m_asEntries[m_nCurChildIndex].XMin == nXMin &&
1347
129
        m_asEntries[m_nCurChildIndex].YMin == nYMin &&
1348
129
        m_asEntries[m_nCurChildIndex].XMax == nXMax &&
1349
129
        m_asEntries[m_nCurChildIndex].YMax == nYMax)
1350
24
    {
1351
24
        return; /* Nothing changed... nothing to do */
1352
24
    }
1353
1354
105
    m_bModified = TRUE;
1355
1356
105
    m_asEntries[m_nCurChildIndex].XMin = nXMin;
1357
105
    m_asEntries[m_nCurChildIndex].YMin = nYMin;
1358
105
    m_asEntries[m_nCurChildIndex].XMax = nXMax;
1359
105
    m_asEntries[m_nCurChildIndex].YMax = nYMax;
1360
1361
105
    m_nMinX = 1000000000;
1362
105
    m_nMinY = 1000000000;
1363
105
    m_nMaxX = -1000000000;
1364
105
    m_nMaxY = -1000000000;
1365
1366
1.02k
    for (int i = 0; i < m_numEntries; i++)
1367
916
    {
1368
916
        if (m_asEntries[i].XMin < m_nMinX)
1369
122
            m_nMinX = m_asEntries[i].XMin;
1370
916
        if (m_asEntries[i].XMax > m_nMaxX)
1371
136
            m_nMaxX = m_asEntries[i].XMax;
1372
1373
916
        if (m_asEntries[i].YMin < m_nMinY)
1374
147
            m_nMinY = m_asEntries[i].YMin;
1375
916
        if (m_asEntries[i].YMax > m_nMaxY)
1376
136
            m_nMaxY = m_asEntries[i].YMax;
1377
916
    }
1378
1379
105
    if (m_poParentRef)
1380
33
        m_poParentRef->UpdateCurChildMBR(m_nMinX, m_nMinY, m_nMaxX, m_nMaxY,
1381
33
                                         GetNodeBlockPtr());
1382
105
}
1383
1384
/**********************************************************************
1385
 *                   TABMAPIndexBlock::SetMAPBlockManagerRef()
1386
 *
1387
 * Pass a reference to the block manager object for the file this
1388
 * block belongs to.  The block manager will be used by this object
1389
 * when it needs to automatically allocate a new block.
1390
 **********************************************************************/
1391
void TABMAPIndexBlock::SetMAPBlockManagerRef(TABBinBlockManager *poBlockMgr)
1392
106
{
1393
106
    m_poBlockManagerRef = poBlockMgr;
1394
106
}
1395
1396
/**********************************************************************
1397
 *                   TABMAPIndexBlock::SetParentRef()
1398
 *
1399
 * Used to pass a reference to this node's parent.
1400
 **********************************************************************/
1401
void TABMAPIndexBlock::SetParentRef(TABMAPIndexBlock *poParent)
1402
61
{
1403
61
    m_poParentRef = poParent;
1404
61
}
1405
1406
/**********************************************************************
1407
 *                   TABMAPIndexBlock::SetCurChild()
1408
 *
1409
 * Used to transfer a child object from one node to another
1410
 **********************************************************************/
1411
void TABMAPIndexBlock::SetCurChild(std::unique_ptr<TABMAPIndexBlock> &&poChild,
1412
                                   int nChildIndex)
1413
1
{
1414
1
    if (poChild)
1415
1
        poChild->SetParentRef(this);
1416
1
    m_poCurChild = std::move(poChild);
1417
1
    m_nCurChildIndex = nChildIndex;
1418
1
}
1419
1420
/**********************************************************************
1421
 *                   TABMAPIndexBlock::Dump()
1422
 *
1423
 * Dump block contents... available only in DEBUG mode.
1424
 **********************************************************************/
1425
#ifdef DEBUG
1426
1427
void TABMAPIndexBlock::Dump(FILE *fpOut /*=NULL*/)
1428
{
1429
    if (fpOut == nullptr)
1430
        fpOut = stdout;
1431
1432
    fprintf(fpOut, "----- TABMAPIndexBlock::Dump() -----\n");
1433
    if (m_pabyBuf == nullptr)
1434
    {
1435
        fprintf(fpOut, "Block has not been initialized yet.");
1436
    }
1437
    else
1438
    {
1439
        fprintf(fpOut, "Index Block (type %d) at offset %d.\n", m_nBlockType,
1440
                m_nFileOffset);
1441
        fprintf(fpOut, "  m_numEntries          = %d\n", m_numEntries);
1442
1443
        /*-------------------------------------------------------------
1444
         * Loop through all entries, dumping each of them
1445
         *------------------------------------------------------------*/
1446
        if (m_numEntries > 0)
1447
            ReadAllEntries();
1448
1449
        for (int i = 0; i < m_numEntries; i++)
1450
        {
1451
            fprintf(fpOut, "    %6d -> (%d, %d) - (%d, %d)\n",
1452
                    m_asEntries[i].nBlockPtr, m_asEntries[i].XMin,
1453
                    m_asEntries[i].YMin, m_asEntries[i].XMax,
1454
                    m_asEntries[i].YMax);
1455
        }
1456
    }
1457
1458
    fflush(fpOut);
1459
}
1460
#endif  // DEBUG