LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/mitab - mitab_mapfile.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 828 1116 74.2 %
Date: 2025-08-19 18:03:11 Functions: 54 61 88.5 %

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

Generated by: LCOV version 1.14