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