/src/gdal/ogr/ogrsf_frmts/shape/shptree.c
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: Shapelib |
4 | | * Purpose: Implementation of quadtree building and searching functions. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 1999, Frank Warmerdam |
9 | | * Copyright (c) 2012-2024, Even Rouault <even dot rouault at spatialys.com> |
10 | | * |
11 | | * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later |
12 | | ****************************************************************************** |
13 | | * |
14 | | */ |
15 | | |
16 | | #include "shapefil_private.h" |
17 | | |
18 | | #include <math.h> |
19 | | #include <assert.h> |
20 | | #include <stdbool.h> |
21 | | #include <stdlib.h> |
22 | | #include <string.h> |
23 | | #include <limits.h> |
24 | | |
25 | | #ifdef USE_CPL |
26 | | #include "cpl_error.h" |
27 | | #endif |
28 | | |
29 | | #ifndef TRUE |
30 | | #define TRUE 1 |
31 | | #define FALSE 0 |
32 | | #endif |
33 | | |
34 | | /* -------------------------------------------------------------------- */ |
35 | | /* If the following is 0.5, nodes will be split in half. If it */ |
36 | | /* is 0.6 then each subnode will contain 60% of the parent */ |
37 | | /* node, with 20% representing overlap. This can be help to */ |
38 | | /* prevent small objects on a boundary from shifting too high */ |
39 | | /* up the tree. */ |
40 | | /* -------------------------------------------------------------------- */ |
41 | | |
42 | 0 | #define SHP_SPLIT_RATIO 0.55 |
43 | | |
44 | | /************************************************************************/ |
45 | | /* SHPTreeNodeInit() */ |
46 | | /* */ |
47 | | /* Initialize a tree node. */ |
48 | | /************************************************************************/ |
49 | | |
50 | | static SHPTreeNode *SHPTreeNodeCreate(const double *padfBoundsMin, |
51 | | const double *padfBoundsMax) |
52 | | |
53 | 0 | { |
54 | 0 | SHPTreeNode *psTreeNode; |
55 | |
|
56 | 0 | psTreeNode = STATIC_CAST(SHPTreeNode *, malloc(sizeof(SHPTreeNode))); |
57 | 0 | if (SHPLIB_NULLPTR == psTreeNode) |
58 | 0 | return SHPLIB_NULLPTR; |
59 | | |
60 | 0 | psTreeNode->nShapeCount = 0; |
61 | 0 | psTreeNode->panShapeIds = SHPLIB_NULLPTR; |
62 | 0 | psTreeNode->papsShapeObj = SHPLIB_NULLPTR; |
63 | |
|
64 | 0 | psTreeNode->nSubNodes = 0; |
65 | |
|
66 | 0 | if (padfBoundsMin != SHPLIB_NULLPTR) |
67 | 0 | memcpy(psTreeNode->adfBoundsMin, padfBoundsMin, sizeof(double) * 4); |
68 | |
|
69 | 0 | if (padfBoundsMax != SHPLIB_NULLPTR) |
70 | 0 | memcpy(psTreeNode->adfBoundsMax, padfBoundsMax, sizeof(double) * 4); |
71 | |
|
72 | 0 | return psTreeNode; |
73 | 0 | } |
74 | | |
75 | | /************************************************************************/ |
76 | | /* SHPCreateTree() */ |
77 | | /************************************************************************/ |
78 | | |
79 | | SHPTree SHPAPI_CALL1(*) |
80 | | SHPCreateTree(SHPHandle hSHP, int nDimension, int nMaxDepth, |
81 | | const double *padfBoundsMin, const double *padfBoundsMax) |
82 | | |
83 | 0 | { |
84 | 0 | SHPTree *psTree; |
85 | |
|
86 | 0 | if (padfBoundsMin == SHPLIB_NULLPTR && hSHP == SHPLIB_NULLPTR) |
87 | 0 | return SHPLIB_NULLPTR; |
88 | | |
89 | | /* -------------------------------------------------------------------- */ |
90 | | /* Allocate the tree object */ |
91 | | /* -------------------------------------------------------------------- */ |
92 | 0 | psTree = STATIC_CAST(SHPTree *, malloc(sizeof(SHPTree))); |
93 | 0 | if (SHPLIB_NULLPTR == psTree) |
94 | 0 | { |
95 | 0 | return SHPLIB_NULLPTR; |
96 | 0 | } |
97 | | |
98 | 0 | psTree->hSHP = hSHP; |
99 | 0 | psTree->nMaxDepth = nMaxDepth; |
100 | 0 | psTree->nDimension = nDimension; |
101 | 0 | psTree->nTotalCount = 0; |
102 | | |
103 | | /* -------------------------------------------------------------------- */ |
104 | | /* If no max depth was defined, try to select a reasonable one */ |
105 | | /* that implies approximately 8 shapes per node. */ |
106 | | /* -------------------------------------------------------------------- */ |
107 | 0 | if (psTree->nMaxDepth == 0 && hSHP != SHPLIB_NULLPTR) |
108 | 0 | { |
109 | 0 | int nMaxNodeCount = 1; |
110 | 0 | int nShapeCount; |
111 | |
|
112 | 0 | SHPGetInfo(hSHP, &nShapeCount, SHPLIB_NULLPTR, SHPLIB_NULLPTR, |
113 | 0 | SHPLIB_NULLPTR); |
114 | 0 | while (nMaxNodeCount * 4 < nShapeCount) |
115 | 0 | { |
116 | 0 | psTree->nMaxDepth += 1; |
117 | 0 | nMaxNodeCount = nMaxNodeCount * 2; |
118 | 0 | } |
119 | |
|
120 | 0 | #ifdef USE_CPL |
121 | 0 | CPLDebug("Shape", "Estimated spatial index tree depth: %d", |
122 | 0 | psTree->nMaxDepth); |
123 | 0 | #endif |
124 | | |
125 | | /* NOTE: Due to problems with memory allocation for deep trees, |
126 | | * automatically estimated depth is limited up to 12 levels. |
127 | | * See Ticket #1594 for detailed discussion. |
128 | | */ |
129 | 0 | if (psTree->nMaxDepth > MAX_DEFAULT_TREE_DEPTH) |
130 | 0 | { |
131 | 0 | psTree->nMaxDepth = MAX_DEFAULT_TREE_DEPTH; |
132 | |
|
133 | 0 | #ifdef USE_CPL |
134 | 0 | CPLDebug( |
135 | 0 | "Shape", |
136 | 0 | "Falling back to max number of allowed index tree levels (%d).", |
137 | 0 | MAX_DEFAULT_TREE_DEPTH); |
138 | 0 | #endif |
139 | 0 | } |
140 | 0 | } |
141 | | |
142 | | /* -------------------------------------------------------------------- */ |
143 | | /* Allocate the root node. */ |
144 | | /* -------------------------------------------------------------------- */ |
145 | 0 | psTree->psRoot = SHPTreeNodeCreate(padfBoundsMin, padfBoundsMax); |
146 | 0 | if (SHPLIB_NULLPTR == psTree->psRoot) |
147 | 0 | { |
148 | 0 | free(psTree); |
149 | 0 | return SHPLIB_NULLPTR; |
150 | 0 | } |
151 | | |
152 | | /* -------------------------------------------------------------------- */ |
153 | | /* Assign the bounds to the root node. If none are passed in, */ |
154 | | /* use the bounds of the provided file otherwise the create */ |
155 | | /* function will have already set the bounds. */ |
156 | | /* -------------------------------------------------------------------- */ |
157 | 0 | if (padfBoundsMin == SHPLIB_NULLPTR) |
158 | 0 | { |
159 | 0 | SHPGetInfo(hSHP, SHPLIB_NULLPTR, SHPLIB_NULLPTR, |
160 | 0 | psTree->psRoot->adfBoundsMin, psTree->psRoot->adfBoundsMax); |
161 | 0 | } |
162 | | |
163 | | /* -------------------------------------------------------------------- */ |
164 | | /* If we have a file, insert all its shapes into the tree. */ |
165 | | /* -------------------------------------------------------------------- */ |
166 | 0 | if (hSHP != SHPLIB_NULLPTR) |
167 | 0 | { |
168 | 0 | int iShape, nShapeCount; |
169 | |
|
170 | 0 | SHPGetInfo(hSHP, &nShapeCount, SHPLIB_NULLPTR, SHPLIB_NULLPTR, |
171 | 0 | SHPLIB_NULLPTR); |
172 | |
|
173 | 0 | for (iShape = 0; iShape < nShapeCount; iShape++) |
174 | 0 | { |
175 | 0 | SHPObject *psShape; |
176 | |
|
177 | 0 | psShape = SHPReadObject(hSHP, iShape); |
178 | 0 | if (psShape != SHPLIB_NULLPTR) |
179 | 0 | { |
180 | 0 | SHPTreeAddShapeId(psTree, psShape); |
181 | 0 | SHPDestroyObject(psShape); |
182 | 0 | } |
183 | 0 | } |
184 | 0 | } |
185 | |
|
186 | 0 | return psTree; |
187 | 0 | } |
188 | | |
189 | | /************************************************************************/ |
190 | | /* SHPDestroyTreeNode() */ |
191 | | /************************************************************************/ |
192 | | |
193 | | static void SHPDestroyTreeNode(SHPTreeNode *psTreeNode) |
194 | | |
195 | 0 | { |
196 | 0 | int i; |
197 | |
|
198 | 0 | assert(SHPLIB_NULLPTR != psTreeNode); |
199 | | |
200 | 0 | for (i = 0; i < psTreeNode->nSubNodes; i++) |
201 | 0 | { |
202 | 0 | if (psTreeNode->apsSubNode[i] != SHPLIB_NULLPTR) |
203 | 0 | SHPDestroyTreeNode(psTreeNode->apsSubNode[i]); |
204 | 0 | } |
205 | |
|
206 | 0 | if (psTreeNode->panShapeIds != SHPLIB_NULLPTR) |
207 | 0 | free(psTreeNode->panShapeIds); |
208 | |
|
209 | 0 | if (psTreeNode->papsShapeObj != SHPLIB_NULLPTR) |
210 | 0 | { |
211 | 0 | for (i = 0; i < psTreeNode->nShapeCount; i++) |
212 | 0 | { |
213 | 0 | if (psTreeNode->papsShapeObj[i] != SHPLIB_NULLPTR) |
214 | 0 | SHPDestroyObject(psTreeNode->papsShapeObj[i]); |
215 | 0 | } |
216 | |
|
217 | 0 | free(psTreeNode->papsShapeObj); |
218 | 0 | } |
219 | |
|
220 | 0 | free(psTreeNode); |
221 | 0 | } |
222 | | |
223 | | /************************************************************************/ |
224 | | /* SHPDestroyTree() */ |
225 | | /************************************************************************/ |
226 | | |
227 | | void SHPAPI_CALL SHPDestroyTree(SHPTree *psTree) |
228 | | |
229 | 0 | { |
230 | 0 | SHPDestroyTreeNode(psTree->psRoot); |
231 | 0 | free(psTree); |
232 | 0 | } |
233 | | |
234 | | /************************************************************************/ |
235 | | /* SHPCheckBoundsOverlap() */ |
236 | | /* */ |
237 | | /* Do the given boxes overlap at all? */ |
238 | | /************************************************************************/ |
239 | | |
240 | | int SHPAPI_CALL SHPCheckBoundsOverlap(const double *padfBox1Min, |
241 | | const double *padfBox1Max, |
242 | | const double *padfBox2Min, |
243 | | const double *padfBox2Max, int nDimension) |
244 | 0 | { |
245 | 0 | for (int iDim = 0; iDim < nDimension; iDim++) |
246 | 0 | { |
247 | 0 | if (padfBox2Max[iDim] < padfBox1Min[iDim]) |
248 | 0 | return FALSE; |
249 | | |
250 | 0 | if (padfBox1Max[iDim] < padfBox2Min[iDim]) |
251 | 0 | return FALSE; |
252 | 0 | } |
253 | | |
254 | 0 | return TRUE; |
255 | 0 | } |
256 | | |
257 | | /************************************************************************/ |
258 | | /* SHPCheckObjectContained() */ |
259 | | /* */ |
260 | | /* Does the given shape fit within the indicated extents? */ |
261 | | /************************************************************************/ |
262 | | |
263 | | static bool SHPCheckObjectContained(const SHPObject *psObject, int nDimension, |
264 | | const double *padfBoundsMin, |
265 | | const double *padfBoundsMax) |
266 | | |
267 | 0 | { |
268 | 0 | if (psObject->dfXMin < padfBoundsMin[0] || |
269 | 0 | psObject->dfXMax > padfBoundsMax[0]) |
270 | 0 | return false; |
271 | | |
272 | 0 | if (psObject->dfYMin < padfBoundsMin[1] || |
273 | 0 | psObject->dfYMax > padfBoundsMax[1]) |
274 | 0 | return false; |
275 | | |
276 | 0 | if (nDimension == 2) |
277 | 0 | return true; |
278 | | |
279 | 0 | if (psObject->dfZMin < padfBoundsMin[2] || |
280 | 0 | psObject->dfZMax > padfBoundsMax[2]) |
281 | 0 | return false; |
282 | | |
283 | 0 | if (nDimension == 3) |
284 | 0 | return true; |
285 | | |
286 | 0 | if (psObject->dfMMin < padfBoundsMin[3] || |
287 | 0 | psObject->dfMMax > padfBoundsMax[3]) |
288 | 0 | return false; |
289 | | |
290 | 0 | return true; |
291 | 0 | } |
292 | | |
293 | | /************************************************************************/ |
294 | | /* SHPTreeSplitBounds() */ |
295 | | /* */ |
296 | | /* Split a region into two subregion evenly, cutting along the */ |
297 | | /* longest dimension. */ |
298 | | /************************************************************************/ |
299 | | |
300 | | static void SHPTreeSplitBounds(const double *padfBoundsMinIn, |
301 | | const double *padfBoundsMaxIn, |
302 | | double *padfBoundsMin1, double *padfBoundsMax1, |
303 | | double *padfBoundsMin2, double *padfBoundsMax2) |
304 | | |
305 | 0 | { |
306 | | /* -------------------------------------------------------------------- */ |
307 | | /* The output bounds will be very similar to the input bounds, */ |
308 | | /* so just copy over to start. */ |
309 | | /* -------------------------------------------------------------------- */ |
310 | 0 | memcpy(padfBoundsMin1, padfBoundsMinIn, sizeof(double) * 4); |
311 | 0 | memcpy(padfBoundsMax1, padfBoundsMaxIn, sizeof(double) * 4); |
312 | 0 | memcpy(padfBoundsMin2, padfBoundsMinIn, sizeof(double) * 4); |
313 | 0 | memcpy(padfBoundsMax2, padfBoundsMaxIn, sizeof(double) * 4); |
314 | | |
315 | | /* -------------------------------------------------------------------- */ |
316 | | /* Split in X direction. */ |
317 | | /* -------------------------------------------------------------------- */ |
318 | 0 | if ((padfBoundsMaxIn[0] - padfBoundsMinIn[0]) > |
319 | 0 | (padfBoundsMaxIn[1] - padfBoundsMinIn[1])) |
320 | 0 | { |
321 | 0 | double dfRange = padfBoundsMaxIn[0] - padfBoundsMinIn[0]; |
322 | |
|
323 | 0 | padfBoundsMax1[0] = padfBoundsMinIn[0] + dfRange * SHP_SPLIT_RATIO; |
324 | 0 | padfBoundsMin2[0] = padfBoundsMaxIn[0] - dfRange * SHP_SPLIT_RATIO; |
325 | 0 | } |
326 | | |
327 | | /* -------------------------------------------------------------------- */ |
328 | | /* Otherwise split in Y direction. */ |
329 | | /* -------------------------------------------------------------------- */ |
330 | 0 | else |
331 | 0 | { |
332 | 0 | double dfRange = padfBoundsMaxIn[1] - padfBoundsMinIn[1]; |
333 | |
|
334 | 0 | padfBoundsMax1[1] = padfBoundsMinIn[1] + dfRange * SHP_SPLIT_RATIO; |
335 | 0 | padfBoundsMin2[1] = padfBoundsMaxIn[1] - dfRange * SHP_SPLIT_RATIO; |
336 | 0 | } |
337 | 0 | } |
338 | | |
339 | | /************************************************************************/ |
340 | | /* SHPTreeNodeAddShapeId() */ |
341 | | /************************************************************************/ |
342 | | |
343 | | static bool SHPTreeNodeAddShapeId(SHPTreeNode *psTreeNode, SHPObject *psObject, |
344 | | int nMaxDepth, int nDimension) |
345 | | |
346 | 0 | { |
347 | 0 | int i; |
348 | | |
349 | | /* -------------------------------------------------------------------- */ |
350 | | /* If there are subnodes, then consider whether this object */ |
351 | | /* will fit in them. */ |
352 | | /* -------------------------------------------------------------------- */ |
353 | 0 | if (nMaxDepth > 1 && psTreeNode->nSubNodes > 0) |
354 | 0 | { |
355 | 0 | for (i = 0; i < psTreeNode->nSubNodes; i++) |
356 | 0 | { |
357 | 0 | if (SHPCheckObjectContained( |
358 | 0 | psObject, nDimension, |
359 | 0 | psTreeNode->apsSubNode[i]->adfBoundsMin, |
360 | 0 | psTreeNode->apsSubNode[i]->adfBoundsMax)) |
361 | 0 | { |
362 | 0 | return SHPTreeNodeAddShapeId(psTreeNode->apsSubNode[i], |
363 | 0 | psObject, nMaxDepth - 1, |
364 | 0 | nDimension); |
365 | 0 | } |
366 | 0 | } |
367 | 0 | } |
368 | | |
369 | | /* -------------------------------------------------------------------- */ |
370 | | /* Otherwise, consider creating four subnodes if could fit into */ |
371 | | /* them, and adding to the appropriate subnode. */ |
372 | | /* -------------------------------------------------------------------- */ |
373 | 0 | #if MAX_SUBNODE == 4 |
374 | 0 | else if (nMaxDepth > 1 && psTreeNode->nSubNodes == 0) |
375 | 0 | { |
376 | 0 | double adfBoundsMinH1[4], adfBoundsMaxH1[4]; |
377 | 0 | double adfBoundsMinH2[4], adfBoundsMaxH2[4]; |
378 | 0 | double adfBoundsMin1[4], adfBoundsMax1[4]; |
379 | 0 | double adfBoundsMin2[4], adfBoundsMax2[4]; |
380 | 0 | double adfBoundsMin3[4], adfBoundsMax3[4]; |
381 | 0 | double adfBoundsMin4[4], adfBoundsMax4[4]; |
382 | |
|
383 | 0 | SHPTreeSplitBounds(psTreeNode->adfBoundsMin, psTreeNode->adfBoundsMax, |
384 | 0 | adfBoundsMinH1, adfBoundsMaxH1, adfBoundsMinH2, |
385 | 0 | adfBoundsMaxH2); |
386 | |
|
387 | 0 | SHPTreeSplitBounds(adfBoundsMinH1, adfBoundsMaxH1, adfBoundsMin1, |
388 | 0 | adfBoundsMax1, adfBoundsMin2, adfBoundsMax2); |
389 | |
|
390 | 0 | SHPTreeSplitBounds(adfBoundsMinH2, adfBoundsMaxH2, adfBoundsMin3, |
391 | 0 | adfBoundsMax3, adfBoundsMin4, adfBoundsMax4); |
392 | |
|
393 | 0 | if (SHPCheckObjectContained(psObject, nDimension, adfBoundsMin1, |
394 | 0 | adfBoundsMax1) || |
395 | 0 | SHPCheckObjectContained(psObject, nDimension, adfBoundsMin2, |
396 | 0 | adfBoundsMax2) || |
397 | 0 | SHPCheckObjectContained(psObject, nDimension, adfBoundsMin3, |
398 | 0 | adfBoundsMax3) || |
399 | 0 | SHPCheckObjectContained(psObject, nDimension, adfBoundsMin4, |
400 | 0 | adfBoundsMax4)) |
401 | 0 | { |
402 | 0 | psTreeNode->nSubNodes = 4; |
403 | 0 | psTreeNode->apsSubNode[0] = |
404 | 0 | SHPTreeNodeCreate(adfBoundsMin1, adfBoundsMax1); |
405 | 0 | psTreeNode->apsSubNode[1] = |
406 | 0 | SHPTreeNodeCreate(adfBoundsMin2, adfBoundsMax2); |
407 | 0 | psTreeNode->apsSubNode[2] = |
408 | 0 | SHPTreeNodeCreate(adfBoundsMin3, adfBoundsMax3); |
409 | 0 | psTreeNode->apsSubNode[3] = |
410 | 0 | SHPTreeNodeCreate(adfBoundsMin4, adfBoundsMax4); |
411 | | |
412 | | /* recurse back on this node now that it has subnodes */ |
413 | 0 | return (SHPTreeNodeAddShapeId(psTreeNode, psObject, nMaxDepth, |
414 | 0 | nDimension)); |
415 | 0 | } |
416 | 0 | } |
417 | 0 | #endif /* MAX_SUBNODE == 4 */ |
418 | | |
419 | | /* -------------------------------------------------------------------- */ |
420 | | /* Otherwise, consider creating two subnodes if could fit into */ |
421 | | /* them, and adding to the appropriate subnode. */ |
422 | | /* -------------------------------------------------------------------- */ |
423 | | #if MAX_SUBNODE == 2 |
424 | | else if (nMaxDepth > 1 && psTreeNode->nSubNodes == 0) |
425 | | { |
426 | | double adfBoundsMin1[4], adfBoundsMax1[4]; |
427 | | double adfBoundsMin2[4], adfBoundsMax2[4]; |
428 | | |
429 | | SHPTreeSplitBounds(psTreeNode->adfBoundsMin, psTreeNode->adfBoundsMax, |
430 | | adfBoundsMin1, adfBoundsMax1, adfBoundsMin2, |
431 | | adfBoundsMax2); |
432 | | |
433 | | if (SHPCheckObjectContained(psObject, nDimension, adfBoundsMin1, |
434 | | adfBoundsMax1)) |
435 | | { |
436 | | psTreeNode->nSubNodes = 2; |
437 | | psTreeNode->apsSubNode[0] = |
438 | | SHPTreeNodeCreate(adfBoundsMin1, adfBoundsMax1); |
439 | | psTreeNode->apsSubNode[1] = |
440 | | SHPTreeNodeCreate(adfBoundsMin2, adfBoundsMax2); |
441 | | |
442 | | return (SHPTreeNodeAddShapeId(psTreeNode->apsSubNode[0], psObject, |
443 | | nMaxDepth - 1, nDimension)); |
444 | | } |
445 | | else if (SHPCheckObjectContained(psObject, nDimension, adfBoundsMin2, |
446 | | adfBoundsMax2)) |
447 | | { |
448 | | psTreeNode->nSubNodes = 2; |
449 | | psTreeNode->apsSubNode[0] = |
450 | | SHPTreeNodeCreate(adfBoundsMin1, adfBoundsMax1); |
451 | | psTreeNode->apsSubNode[1] = |
452 | | SHPTreeNodeCreate(adfBoundsMin2, adfBoundsMax2); |
453 | | |
454 | | return (SHPTreeNodeAddShapeId(psTreeNode->apsSubNode[1], psObject, |
455 | | nMaxDepth - 1, nDimension)); |
456 | | } |
457 | | } |
458 | | #endif /* MAX_SUBNODE == 2 */ |
459 | | |
460 | | /* -------------------------------------------------------------------- */ |
461 | | /* If none of that worked, just add it to this nodes list. */ |
462 | | /* -------------------------------------------------------------------- */ |
463 | 0 | psTreeNode->nShapeCount++; |
464 | |
|
465 | 0 | psTreeNode->panShapeIds = |
466 | 0 | STATIC_CAST(int *, realloc(psTreeNode->panShapeIds, |
467 | 0 | sizeof(int) * psTreeNode->nShapeCount)); |
468 | 0 | psTreeNode->panShapeIds[psTreeNode->nShapeCount - 1] = psObject->nShapeId; |
469 | |
|
470 | 0 | if (psTreeNode->papsShapeObj != SHPLIB_NULLPTR) |
471 | 0 | { |
472 | 0 | psTreeNode->papsShapeObj = STATIC_CAST( |
473 | 0 | SHPObject **, realloc(psTreeNode->papsShapeObj, |
474 | 0 | sizeof(void *) * psTreeNode->nShapeCount)); |
475 | 0 | psTreeNode->papsShapeObj[psTreeNode->nShapeCount - 1] = SHPLIB_NULLPTR; |
476 | 0 | } |
477 | |
|
478 | 0 | return true; |
479 | 0 | } |
480 | | |
481 | | /************************************************************************/ |
482 | | /* SHPTreeAddShapeId() */ |
483 | | /* */ |
484 | | /* Add a shape to the tree, but don't keep a pointer to the */ |
485 | | /* object data, just keep the shapeid. */ |
486 | | /************************************************************************/ |
487 | | |
488 | | int SHPAPI_CALL SHPTreeAddShapeId(SHPTree *psTree, SHPObject *psObject) |
489 | | |
490 | 0 | { |
491 | 0 | psTree->nTotalCount++; |
492 | |
|
493 | 0 | return (SHPTreeNodeAddShapeId(psTree->psRoot, psObject, psTree->nMaxDepth, |
494 | 0 | psTree->nDimension)); |
495 | 0 | } |
496 | | |
497 | | /************************************************************************/ |
498 | | /* SHPTreeCollectShapesIds() */ |
499 | | /* */ |
500 | | /* Work function implementing SHPTreeFindLikelyShapes() on a */ |
501 | | /* tree node by tree node basis. */ |
502 | | /************************************************************************/ |
503 | | |
504 | | static void SHPTreeCollectShapeIds(const SHPTree *hTree, |
505 | | const SHPTreeNode *psTreeNode, |
506 | | double *padfBoundsMin, double *padfBoundsMax, |
507 | | int *pnShapeCount, int *pnMaxShapes, |
508 | | int **ppanShapeList) |
509 | | |
510 | 0 | { |
511 | 0 | int i; |
512 | | |
513 | | /* -------------------------------------------------------------------- */ |
514 | | /* Does this node overlap the area of interest at all? If not, */ |
515 | | /* return without adding to the list at all. */ |
516 | | /* -------------------------------------------------------------------- */ |
517 | 0 | if (!SHPCheckBoundsOverlap(psTreeNode->adfBoundsMin, |
518 | 0 | psTreeNode->adfBoundsMax, padfBoundsMin, |
519 | 0 | padfBoundsMax, hTree->nDimension)) |
520 | 0 | return; |
521 | | |
522 | | /* -------------------------------------------------------------------- */ |
523 | | /* Grow the list to hold the shapes on this node. */ |
524 | | /* -------------------------------------------------------------------- */ |
525 | 0 | if (*pnShapeCount + psTreeNode->nShapeCount > *pnMaxShapes) |
526 | 0 | { |
527 | 0 | *pnMaxShapes = (*pnShapeCount + psTreeNode->nShapeCount) * 2 + 20; |
528 | 0 | *ppanShapeList = STATIC_CAST( |
529 | 0 | int *, realloc(*ppanShapeList, sizeof(int) * *pnMaxShapes)); |
530 | 0 | } |
531 | | |
532 | | /* -------------------------------------------------------------------- */ |
533 | | /* Add the local nodes shapeids to the list. */ |
534 | | /* -------------------------------------------------------------------- */ |
535 | 0 | for (i = 0; i < psTreeNode->nShapeCount; i++) |
536 | 0 | { |
537 | 0 | (*ppanShapeList)[(*pnShapeCount)++] = psTreeNode->panShapeIds[i]; |
538 | 0 | } |
539 | | |
540 | | /* -------------------------------------------------------------------- */ |
541 | | /* Recurse to subnodes if they exist. */ |
542 | | /* -------------------------------------------------------------------- */ |
543 | 0 | for (i = 0; i < psTreeNode->nSubNodes; i++) |
544 | 0 | { |
545 | 0 | if (psTreeNode->apsSubNode[i] != SHPLIB_NULLPTR) |
546 | 0 | SHPTreeCollectShapeIds(hTree, psTreeNode->apsSubNode[i], |
547 | 0 | padfBoundsMin, padfBoundsMax, pnShapeCount, |
548 | 0 | pnMaxShapes, ppanShapeList); |
549 | 0 | } |
550 | 0 | } |
551 | | |
552 | | /************************************************************************/ |
553 | | /* SHPTreeFindLikelyShapes() */ |
554 | | /* */ |
555 | | /* Find all shapes within tree nodes for which the tree node */ |
556 | | /* bounding box overlaps the search box. The return value is */ |
557 | | /* an array of shapeids terminated by a -1. The shapeids will */ |
558 | | /* be in order, as hopefully this will result in faster (more */ |
559 | | /* sequential) reading from the file. */ |
560 | | /************************************************************************/ |
561 | | |
562 | | /* helper for qsort */ |
563 | | static int SHPTreeCompareInts(const void *a, const void *b) |
564 | 0 | { |
565 | 0 | return *REINTERPRET_CAST(const int *, a) - |
566 | 0 | *REINTERPRET_CAST(const int *, b); |
567 | 0 | } |
568 | | |
569 | | int SHPAPI_CALL1(*) |
570 | | SHPTreeFindLikelyShapes(const SHPTree *hTree, double *padfBoundsMin, |
571 | | double *padfBoundsMax, int *pnShapeCount) |
572 | | |
573 | 0 | { |
574 | 0 | int *panShapeList = SHPLIB_NULLPTR, nMaxShapes = 0; |
575 | | |
576 | | /* -------------------------------------------------------------------- */ |
577 | | /* Perform the search by recursive descent. */ |
578 | | /* -------------------------------------------------------------------- */ |
579 | 0 | *pnShapeCount = 0; |
580 | |
|
581 | 0 | SHPTreeCollectShapeIds(hTree, hTree->psRoot, padfBoundsMin, padfBoundsMax, |
582 | 0 | pnShapeCount, &nMaxShapes, &panShapeList); |
583 | | |
584 | | /* -------------------------------------------------------------------- */ |
585 | | /* Sort the id array */ |
586 | | /* -------------------------------------------------------------------- */ |
587 | |
|
588 | 0 | if (panShapeList != SHPLIB_NULLPTR) |
589 | 0 | qsort(panShapeList, *pnShapeCount, sizeof(int), SHPTreeCompareInts); |
590 | |
|
591 | 0 | return panShapeList; |
592 | 0 | } |
593 | | |
594 | | /************************************************************************/ |
595 | | /* SHPTreeNodeTrim() */ |
596 | | /* */ |
597 | | /* This is the recursive version of SHPTreeTrimExtraNodes() that */ |
598 | | /* walks the tree cleaning it up. */ |
599 | | /************************************************************************/ |
600 | | |
601 | | static int SHPTreeNodeTrim(SHPTreeNode *psTreeNode) |
602 | 0 | { |
603 | 0 | int i; |
604 | | |
605 | | /* -------------------------------------------------------------------- */ |
606 | | /* Trim subtrees, and free subnodes that come back empty. */ |
607 | | /* -------------------------------------------------------------------- */ |
608 | 0 | for (i = 0; i < psTreeNode->nSubNodes; i++) |
609 | 0 | { |
610 | 0 | if (SHPTreeNodeTrim(psTreeNode->apsSubNode[i])) |
611 | 0 | { |
612 | 0 | SHPDestroyTreeNode(psTreeNode->apsSubNode[i]); |
613 | |
|
614 | 0 | psTreeNode->apsSubNode[i] = |
615 | 0 | psTreeNode->apsSubNode[psTreeNode->nSubNodes - 1]; |
616 | |
|
617 | 0 | psTreeNode->nSubNodes--; |
618 | |
|
619 | 0 | i--; /* process the new occupant of this subnode entry */ |
620 | 0 | } |
621 | 0 | } |
622 | | |
623 | | /* -------------------------------------------------------------------- */ |
624 | | /* If the current node has 1 subnode and no shapes, promote that */ |
625 | | /* subnode to the current node position. */ |
626 | | /* -------------------------------------------------------------------- */ |
627 | 0 | if (psTreeNode->nSubNodes == 1 && psTreeNode->nShapeCount == 0) |
628 | 0 | { |
629 | 0 | SHPTreeNode *psSubNode = psTreeNode->apsSubNode[0]; |
630 | |
|
631 | 0 | memcpy(psTreeNode->adfBoundsMin, psSubNode->adfBoundsMin, |
632 | 0 | sizeof(psSubNode->adfBoundsMin)); |
633 | 0 | memcpy(psTreeNode->adfBoundsMax, psSubNode->adfBoundsMax, |
634 | 0 | sizeof(psSubNode->adfBoundsMax)); |
635 | 0 | psTreeNode->nShapeCount = psSubNode->nShapeCount; |
636 | 0 | assert(psTreeNode->panShapeIds == SHPLIB_NULLPTR); |
637 | 0 | psTreeNode->panShapeIds = psSubNode->panShapeIds; |
638 | 0 | assert(psTreeNode->papsShapeObj == SHPLIB_NULLPTR); |
639 | 0 | psTreeNode->papsShapeObj = psSubNode->papsShapeObj; |
640 | 0 | psTreeNode->nSubNodes = psSubNode->nSubNodes; |
641 | 0 | for (i = 0; i < psSubNode->nSubNodes; i++) |
642 | 0 | psTreeNode->apsSubNode[i] = psSubNode->apsSubNode[i]; |
643 | 0 | free(psSubNode); |
644 | 0 | } |
645 | | |
646 | | /* -------------------------------------------------------------------- */ |
647 | | /* We should be trimmed if we have no subnodes, and no shapes. */ |
648 | | /* -------------------------------------------------------------------- */ |
649 | 0 | return (psTreeNode->nSubNodes == 0 && psTreeNode->nShapeCount == 0); |
650 | 0 | } |
651 | | |
652 | | /************************************************************************/ |
653 | | /* SHPTreeTrimExtraNodes() */ |
654 | | /* */ |
655 | | /* Trim empty nodes from the tree. Note that we never trim an */ |
656 | | /* empty root node. */ |
657 | | /************************************************************************/ |
658 | | |
659 | | void SHPAPI_CALL SHPTreeTrimExtraNodes(SHPTree *hTree) |
660 | 0 | { |
661 | 0 | SHPTreeNodeTrim(hTree->psRoot); |
662 | 0 | } |
663 | | |
664 | | struct SHPDiskTreeInfo |
665 | | { |
666 | | SAHooks sHooks; |
667 | | SAFile fpQIX; |
668 | | }; |
669 | | |
670 | | /************************************************************************/ |
671 | | /* SHPOpenDiskTree() */ |
672 | | /************************************************************************/ |
673 | | |
674 | | SHPTreeDiskHandle SHPOpenDiskTree(const char *pszQIXFilename, |
675 | | const SAHooks *psHooks) |
676 | 0 | { |
677 | 0 | SHPTreeDiskHandle hDiskTree; |
678 | |
|
679 | 0 | hDiskTree = STATIC_CAST(SHPTreeDiskHandle, |
680 | 0 | calloc(1, sizeof(struct SHPDiskTreeInfo))); |
681 | 0 | if (!hDiskTree) |
682 | 0 | return SHPLIB_NULLPTR; |
683 | | |
684 | 0 | if (psHooks == SHPLIB_NULLPTR) |
685 | 0 | SASetupDefaultHooks(&(hDiskTree->sHooks)); |
686 | 0 | else |
687 | 0 | memcpy(&(hDiskTree->sHooks), psHooks, sizeof(SAHooks)); |
688 | |
|
689 | 0 | hDiskTree->fpQIX = hDiskTree->sHooks.FOpen(pszQIXFilename, "rb", |
690 | 0 | hDiskTree->sHooks.pvUserData); |
691 | 0 | if (hDiskTree->fpQIX == SHPLIB_NULLPTR) |
692 | 0 | { |
693 | 0 | free(hDiskTree); |
694 | 0 | return SHPLIB_NULLPTR; |
695 | 0 | } |
696 | | |
697 | 0 | return hDiskTree; |
698 | 0 | } |
699 | | |
700 | | /***********************************************************************/ |
701 | | /* SHPCloseDiskTree() */ |
702 | | /************************************************************************/ |
703 | | |
704 | | void SHPCloseDiskTree(SHPTreeDiskHandle hDiskTree) |
705 | 0 | { |
706 | 0 | if (hDiskTree == SHPLIB_NULLPTR) |
707 | 0 | return; |
708 | | |
709 | 0 | hDiskTree->sHooks.FClose(hDiskTree->fpQIX); |
710 | 0 | free(hDiskTree); |
711 | 0 | } |
712 | | |
713 | | /************************************************************************/ |
714 | | /* SHPSearchDiskTreeNode() */ |
715 | | /************************************************************************/ |
716 | | |
717 | | static bool SHPSearchDiskTreeNode(const SHPTreeDiskHandle hDiskTree, |
718 | | double *padfBoundsMin, double *padfBoundsMax, |
719 | | int **ppanResultBuffer, int *pnBufferMax, |
720 | | int *pnResultCount, int bNeedSwap, |
721 | | int nRecLevel) |
722 | | |
723 | 0 | { |
724 | 0 | unsigned int i; |
725 | 0 | unsigned int offset; |
726 | 0 | unsigned int numshapes, numsubnodes; |
727 | 0 | double adfNodeBoundsMin[2], adfNodeBoundsMax[2]; |
728 | 0 | int nFReadAcc; |
729 | | |
730 | | /* -------------------------------------------------------------------- */ |
731 | | /* Read and unswap first part of node info. */ |
732 | | /* -------------------------------------------------------------------- */ |
733 | 0 | nFReadAcc = STATIC_CAST( |
734 | 0 | int, hDiskTree->sHooks.FRead(&offset, 4, 1, hDiskTree->fpQIX)); |
735 | 0 | if (bNeedSwap) |
736 | 0 | SHP_SWAP32(&offset); |
737 | |
|
738 | 0 | nFReadAcc += STATIC_CAST(int, hDiskTree->sHooks.FRead(adfNodeBoundsMin, |
739 | 0 | sizeof(double), 2, |
740 | 0 | hDiskTree->fpQIX)); |
741 | 0 | nFReadAcc += STATIC_CAST(int, hDiskTree->sHooks.FRead(adfNodeBoundsMax, |
742 | 0 | sizeof(double), 2, |
743 | 0 | hDiskTree->fpQIX)); |
744 | 0 | if (bNeedSwap) |
745 | 0 | { |
746 | 0 | SHP_SWAPDOUBLE(adfNodeBoundsMin + 0); |
747 | 0 | SHP_SWAPDOUBLE(adfNodeBoundsMin + 1); |
748 | 0 | SHP_SWAPDOUBLE(adfNodeBoundsMax + 0); |
749 | 0 | SHP_SWAPDOUBLE(adfNodeBoundsMax + 1); |
750 | 0 | } |
751 | |
|
752 | 0 | nFReadAcc += STATIC_CAST( |
753 | 0 | int, hDiskTree->sHooks.FRead(&numshapes, 4, 1, hDiskTree->fpQIX)); |
754 | 0 | if (bNeedSwap) |
755 | 0 | SHP_SWAP32(&numshapes); |
756 | | |
757 | | /* Check that we could read all previous values */ |
758 | 0 | if (nFReadAcc != 1 + 2 + 2 + 1) |
759 | 0 | { |
760 | 0 | hDiskTree->sHooks.Error("I/O error"); |
761 | 0 | return false; |
762 | 0 | } |
763 | | |
764 | | /* Sanity checks to avoid int overflows in later computation */ |
765 | 0 | if (offset > INT_MAX - sizeof(int)) |
766 | 0 | { |
767 | 0 | hDiskTree->sHooks.Error("Invalid value for offset"); |
768 | 0 | return false; |
769 | 0 | } |
770 | | |
771 | 0 | if (numshapes > (INT_MAX - offset - sizeof(int)) / sizeof(int) || |
772 | 0 | numshapes > INT_MAX / sizeof(int) - *pnResultCount) |
773 | 0 | { |
774 | 0 | hDiskTree->sHooks.Error("Invalid value for numshapes"); |
775 | 0 | return false; |
776 | 0 | } |
777 | | |
778 | | /* -------------------------------------------------------------------- */ |
779 | | /* If we don't overlap this node at all, we can just fseek() */ |
780 | | /* pass this node info and all subnodes. */ |
781 | | /* -------------------------------------------------------------------- */ |
782 | 0 | if (!SHPCheckBoundsOverlap(adfNodeBoundsMin, adfNodeBoundsMax, |
783 | 0 | padfBoundsMin, padfBoundsMax, 2)) |
784 | 0 | { |
785 | 0 | offset += numshapes * sizeof(int) + sizeof(int); |
786 | 0 | hDiskTree->sHooks.FSeek(hDiskTree->fpQIX, offset, SEEK_CUR); |
787 | 0 | return true; |
788 | 0 | } |
789 | | |
790 | | /* -------------------------------------------------------------------- */ |
791 | | /* Add all the shapeids at this node to our list. */ |
792 | | /* -------------------------------------------------------------------- */ |
793 | 0 | if (numshapes > 0) |
794 | 0 | { |
795 | 0 | if (*pnResultCount + numshapes > |
796 | 0 | STATIC_CAST(unsigned int, *pnBufferMax)) |
797 | 0 | { |
798 | 0 | int *pNewBuffer; |
799 | |
|
800 | 0 | *pnBufferMax = (*pnResultCount + numshapes + 100) * 5 / 4; |
801 | |
|
802 | 0 | if (STATIC_CAST(size_t, *pnBufferMax) > INT_MAX / sizeof(int)) |
803 | 0 | *pnBufferMax = *pnResultCount + numshapes; |
804 | |
|
805 | 0 | pNewBuffer = STATIC_CAST( |
806 | 0 | int *, realloc(*ppanResultBuffer, *pnBufferMax * sizeof(int))); |
807 | |
|
808 | 0 | if (pNewBuffer == SHPLIB_NULLPTR) |
809 | 0 | { |
810 | 0 | hDiskTree->sHooks.Error("Out of memory error"); |
811 | 0 | return false; |
812 | 0 | } |
813 | | |
814 | 0 | *ppanResultBuffer = pNewBuffer; |
815 | 0 | } |
816 | | |
817 | 0 | if (hDiskTree->sHooks.FRead(*ppanResultBuffer + *pnResultCount, |
818 | 0 | sizeof(int), numshapes, |
819 | 0 | hDiskTree->fpQIX) != numshapes) |
820 | 0 | { |
821 | 0 | hDiskTree->sHooks.Error("I/O error"); |
822 | 0 | return false; |
823 | 0 | } |
824 | | |
825 | 0 | if (bNeedSwap) |
826 | 0 | { |
827 | 0 | for (i = 0; i < numshapes; i++) |
828 | 0 | SHP_SWAP32(*ppanResultBuffer + *pnResultCount + i); |
829 | 0 | } |
830 | |
|
831 | 0 | *pnResultCount += numshapes; |
832 | 0 | } |
833 | | |
834 | | /* -------------------------------------------------------------------- */ |
835 | | /* Process the subnodes. */ |
836 | | /* -------------------------------------------------------------------- */ |
837 | 0 | if (hDiskTree->sHooks.FRead(&numsubnodes, 4, 1, hDiskTree->fpQIX) != 1) |
838 | 0 | { |
839 | 0 | hDiskTree->sHooks.Error("I/O error"); |
840 | 0 | return false; |
841 | 0 | } |
842 | 0 | if (bNeedSwap) |
843 | 0 | SHP_SWAP32(&numsubnodes); |
844 | 0 | if (numsubnodes > 0 && nRecLevel == 32) |
845 | 0 | { |
846 | 0 | hDiskTree->sHooks.Error("Shape tree is too deep"); |
847 | 0 | return false; |
848 | 0 | } |
849 | | |
850 | 0 | for (i = 0; i < numsubnodes; i++) |
851 | 0 | { |
852 | 0 | if (!SHPSearchDiskTreeNode(hDiskTree, padfBoundsMin, padfBoundsMax, |
853 | 0 | ppanResultBuffer, pnBufferMax, pnResultCount, |
854 | 0 | bNeedSwap, nRecLevel + 1)) |
855 | 0 | return false; |
856 | 0 | } |
857 | | |
858 | 0 | return true; |
859 | 0 | } |
860 | | |
861 | | /************************************************************************/ |
862 | | /* SHPTreeReadLibc() */ |
863 | | /************************************************************************/ |
864 | | |
865 | | static SAOffset SHPTreeReadLibc(void *p, SAOffset size, SAOffset nmemb, |
866 | | SAFile file) |
867 | | |
868 | 0 | { |
869 | 0 | return STATIC_CAST(SAOffset, fread(p, STATIC_CAST(size_t, size), |
870 | 0 | STATIC_CAST(size_t, nmemb), |
871 | 0 | REINTERPRET_CAST(FILE *, file))); |
872 | 0 | } |
873 | | |
874 | | /************************************************************************/ |
875 | | /* SHPTreeSeekLibc() */ |
876 | | /************************************************************************/ |
877 | | |
878 | | static SAOffset SHPTreeSeekLibc(SAFile file, SAOffset offset, int whence) |
879 | | |
880 | 0 | { |
881 | 0 | return STATIC_CAST(SAOffset, fseek(REINTERPRET_CAST(FILE *, file), |
882 | 0 | STATIC_CAST(long, offset), whence)); |
883 | 0 | } |
884 | | |
885 | | /************************************************************************/ |
886 | | /* SHPSearchDiskTree() */ |
887 | | /************************************************************************/ |
888 | | |
889 | | int SHPAPI_CALL1(*) SHPSearchDiskTree(FILE *fp, double *padfBoundsMin, |
890 | | double *padfBoundsMax, int *pnShapeCount) |
891 | 0 | { |
892 | 0 | struct SHPDiskTreeInfo sDiskTree; |
893 | 0 | memset(&sDiskTree.sHooks, 0, sizeof(sDiskTree.sHooks)); |
894 | | |
895 | | /* We do not use SASetupDefaultHooks() because the FILE* */ |
896 | | /* is a libc FILE* */ |
897 | 0 | sDiskTree.sHooks.FSeek = SHPTreeSeekLibc; |
898 | 0 | sDiskTree.sHooks.FRead = SHPTreeReadLibc; |
899 | |
|
900 | 0 | sDiskTree.fpQIX = REINTERPRET_CAST(SAFile, fp); |
901 | |
|
902 | 0 | return SHPSearchDiskTreeEx(&sDiskTree, padfBoundsMin, padfBoundsMax, |
903 | 0 | pnShapeCount); |
904 | 0 | } |
905 | | |
906 | | /***********************************************************************/ |
907 | | /* SHPSearchDiskTreeEx() */ |
908 | | /************************************************************************/ |
909 | | |
910 | | int SHPAPI_CALL1(*) |
911 | | SHPSearchDiskTreeEx(const SHPTreeDiskHandle hDiskTree, |
912 | | double *padfBoundsMin, double *padfBoundsMax, |
913 | | int *pnShapeCount) |
914 | | |
915 | 0 | { |
916 | 0 | int nBufferMax = 0; |
917 | 0 | unsigned char abyBuf[16]; |
918 | 0 | int *panResultBuffer = SHPLIB_NULLPTR; |
919 | |
|
920 | 0 | *pnShapeCount = 0; |
921 | | |
922 | | /* -------------------------------------------------------------------- */ |
923 | | /* Read the header. */ |
924 | | /* -------------------------------------------------------------------- */ |
925 | 0 | hDiskTree->sHooks.FSeek(hDiskTree->fpQIX, 0, SEEK_SET); |
926 | 0 | hDiskTree->sHooks.FRead(abyBuf, 16, 1, hDiskTree->fpQIX); |
927 | |
|
928 | 0 | if (memcmp(abyBuf, "SQT", 3) != 0) |
929 | 0 | return SHPLIB_NULLPTR; |
930 | | |
931 | | #if defined(SHP_BIG_ENDIAN) |
932 | | bool bNeedSwap = abyBuf[3] != 2; |
933 | | #else |
934 | 0 | bool bNeedSwap = abyBuf[3] != 1; |
935 | 0 | #endif |
936 | | |
937 | | /* -------------------------------------------------------------------- */ |
938 | | /* Search through root node and its descendants. */ |
939 | | /* -------------------------------------------------------------------- */ |
940 | 0 | if (!SHPSearchDiskTreeNode(hDiskTree, padfBoundsMin, padfBoundsMax, |
941 | 0 | &panResultBuffer, &nBufferMax, pnShapeCount, |
942 | 0 | bNeedSwap, 0)) |
943 | 0 | { |
944 | 0 | if (panResultBuffer != SHPLIB_NULLPTR) |
945 | 0 | free(panResultBuffer); |
946 | 0 | *pnShapeCount = 0; |
947 | 0 | return SHPLIB_NULLPTR; |
948 | 0 | } |
949 | | /* -------------------------------------------------------------------- */ |
950 | | /* Sort the id array */ |
951 | | /* -------------------------------------------------------------------- */ |
952 | | |
953 | | /* To distinguish between empty intersection from error case */ |
954 | 0 | if (panResultBuffer == SHPLIB_NULLPTR) |
955 | 0 | panResultBuffer = STATIC_CAST(int *, calloc(1, sizeof(int))); |
956 | 0 | else |
957 | 0 | qsort(panResultBuffer, *pnShapeCount, sizeof(int), SHPTreeCompareInts); |
958 | |
|
959 | 0 | return panResultBuffer; |
960 | 0 | } |
961 | | |
962 | | /************************************************************************/ |
963 | | /* SHPGetSubNodeOffset() */ |
964 | | /* */ |
965 | | /* Determine how big all the subnodes of this node (and their */ |
966 | | /* children) will be. This will allow disk based searchers to */ |
967 | | /* seek past them all efficiently. */ |
968 | | /************************************************************************/ |
969 | | |
970 | | static int SHPGetSubNodeOffset(SHPTreeNode *node) |
971 | 0 | { |
972 | 0 | int i; |
973 | 0 | int offset = 0; |
974 | |
|
975 | 0 | for (i = 0; i < node->nSubNodes; i++) |
976 | 0 | { |
977 | 0 | if (node->apsSubNode[i]) |
978 | 0 | { |
979 | 0 | offset += 4 * sizeof(double) + |
980 | 0 | (node->apsSubNode[i]->nShapeCount + 3) * sizeof(int); |
981 | 0 | offset += SHPGetSubNodeOffset(node->apsSubNode[i]); |
982 | 0 | } |
983 | 0 | } |
984 | |
|
985 | 0 | return (offset); |
986 | 0 | } |
987 | | |
988 | | /************************************************************************/ |
989 | | /* SHPWriteTreeNode() */ |
990 | | /************************************************************************/ |
991 | | |
992 | | static void SHPWriteTreeNode(SAFile fp, SHPTreeNode *node, |
993 | | const SAHooks *psHooks) |
994 | 0 | { |
995 | 0 | int i, j; |
996 | 0 | int offset; |
997 | 0 | unsigned char *pabyRec; |
998 | 0 | assert(SHPLIB_NULLPTR != node); |
999 | | |
1000 | 0 | offset = SHPGetSubNodeOffset(node); |
1001 | |
|
1002 | 0 | pabyRec = STATIC_CAST(unsigned char *, |
1003 | 0 | malloc(sizeof(double) * 4 + (3 * sizeof(int)) + |
1004 | 0 | (node->nShapeCount * sizeof(int)))); |
1005 | 0 | if (SHPLIB_NULLPTR == pabyRec) |
1006 | 0 | { |
1007 | 0 | #ifdef USE_CPL |
1008 | 0 | CPLError(CE_Fatal, CPLE_OutOfMemory, "Memory allocation failure"); |
1009 | 0 | #endif |
1010 | 0 | assert(0); |
1011 | 0 | return; |
1012 | 0 | } |
1013 | | |
1014 | 0 | memcpy(pabyRec, &offset, 4); |
1015 | | |
1016 | | /* minx, miny, maxx, maxy */ |
1017 | 0 | memcpy(pabyRec + 4, node->adfBoundsMin + 0, sizeof(double)); |
1018 | 0 | memcpy(pabyRec + 12, node->adfBoundsMin + 1, sizeof(double)); |
1019 | 0 | memcpy(pabyRec + 20, node->adfBoundsMax + 0, sizeof(double)); |
1020 | 0 | memcpy(pabyRec + 28, node->adfBoundsMax + 1, sizeof(double)); |
1021 | |
|
1022 | 0 | memcpy(pabyRec + 36, &node->nShapeCount, 4); |
1023 | 0 | j = node->nShapeCount * sizeof(int); |
1024 | 0 | if (j) |
1025 | 0 | memcpy(pabyRec + 40, node->panShapeIds, j); |
1026 | 0 | memcpy(pabyRec + j + 40, &node->nSubNodes, 4); |
1027 | |
|
1028 | 0 | psHooks->FWrite(pabyRec, 44 + j, 1, fp); |
1029 | 0 | free(pabyRec); |
1030 | |
|
1031 | 0 | for (i = 0; i < node->nSubNodes; i++) |
1032 | 0 | { |
1033 | 0 | if (node->apsSubNode[i]) |
1034 | 0 | SHPWriteTreeNode(fp, node->apsSubNode[i], psHooks); |
1035 | 0 | } |
1036 | 0 | } |
1037 | | |
1038 | | /************************************************************************/ |
1039 | | /* SHPWriteTree() */ |
1040 | | /************************************************************************/ |
1041 | | |
1042 | | int SHPAPI_CALL SHPWriteTree(SHPTree *tree, const char *filename) |
1043 | 0 | { |
1044 | 0 | SAHooks sHooks; |
1045 | |
|
1046 | 0 | SASetupDefaultHooks(&sHooks); |
1047 | |
|
1048 | 0 | return SHPWriteTreeLL(tree, filename, &sHooks); |
1049 | 0 | } |
1050 | | |
1051 | | /************************************************************************/ |
1052 | | /* SHPWriteTreeLL() */ |
1053 | | /************************************************************************/ |
1054 | | |
1055 | | int SHPWriteTreeLL(SHPTree *tree, const char *filename, const SAHooks *psHooks) |
1056 | 0 | { |
1057 | 0 | const char signature[4] = "SQT"; |
1058 | 0 | char abyBuf[32]; |
1059 | 0 | SAFile fp; |
1060 | |
|
1061 | 0 | SAHooks sHooks; |
1062 | 0 | if (psHooks == SHPLIB_NULLPTR) |
1063 | 0 | { |
1064 | 0 | SASetupDefaultHooks(&sHooks); |
1065 | 0 | psHooks = &sHooks; |
1066 | 0 | } |
1067 | | |
1068 | | /* -------------------------------------------------------------------- */ |
1069 | | /* Open the output file. */ |
1070 | | /* -------------------------------------------------------------------- */ |
1071 | 0 | fp = psHooks->FOpen(filename, "wb", psHooks->pvUserData); |
1072 | 0 | if (fp == SHPLIB_NULLPTR) |
1073 | 0 | { |
1074 | 0 | return FALSE; |
1075 | 0 | } |
1076 | | |
1077 | | /* -------------------------------------------------------------------- */ |
1078 | | /* Write the header. */ |
1079 | | /* -------------------------------------------------------------------- */ |
1080 | 0 | memcpy(abyBuf + 0, signature, 3); |
1081 | |
|
1082 | | #if defined(SHP_BIG_ENDIAN) |
1083 | | abyBuf[3] = 2; /* New MSB */ |
1084 | | #else |
1085 | 0 | abyBuf[3] = 1; /* New LSB */ |
1086 | 0 | #endif |
1087 | |
|
1088 | 0 | abyBuf[4] = 1; /* version */ |
1089 | 0 | abyBuf[5] = 0; /* next 3 reserved */ |
1090 | 0 | abyBuf[6] = 0; |
1091 | 0 | abyBuf[7] = 0; |
1092 | |
|
1093 | 0 | psHooks->FWrite(abyBuf, 8, 1, fp); |
1094 | |
|
1095 | 0 | psHooks->FWrite(&(tree->nTotalCount), 4, 1, fp); |
1096 | | |
1097 | | /* write maxdepth */ |
1098 | |
|
1099 | 0 | psHooks->FWrite(&(tree->nMaxDepth), 4, 1, fp); |
1100 | | |
1101 | | /* -------------------------------------------------------------------- */ |
1102 | | /* Write all the nodes "in order". */ |
1103 | | /* -------------------------------------------------------------------- */ |
1104 | |
|
1105 | 0 | SHPWriteTreeNode(fp, tree->psRoot, psHooks); |
1106 | |
|
1107 | 0 | psHooks->FClose(fp); |
1108 | |
|
1109 | 0 | return TRUE; |
1110 | 0 | } |