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

Generated by: LCOV version 1.14