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