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