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