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