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

Generated by: LCOV version 1.14