1 /*-------------------------------------------------------------------------
4 * SP-GiST implementation of 4-dimensional quad tree over boxes
6 * This module provides SP-GiST implementation for boxes using quad tree
7 * analogy in 4-dimensional space. SP-GiST doesn't allow indexing of
8 * overlapping objects. We are making 2D objects never-overlapping in
9 * 4D space. This technique has some benefits compared to traditional
10 * R-Tree which is implemented as GiST. The performance tests reveal
11 * that this technique especially beneficial with too much overlapping
12 * objects, so called "spaghetti data".
14 * Unlike the original quad tree, we are splitting the tree into 16
15 * quadrants in 4D space. It is easier to imagine it as splitting space
23 * -------------+-------------
30 * We are using box datatype as the prefix, but we are treating them
31 * as points in 4-dimensional space, because 2D boxes are not enough
32 * to represent the quadrant boundaries in 4D space. They however are
33 * sufficient to point out the additional boundaries of the next
36 * We are using traversal values provided by SP-GiST to calculate and
37 * to store the bounds of the quadrants, while traversing into the tree.
38 * Traversal value has all the boundaries in the 4D space, and is is
39 * capable of transferring the required boundaries to the following
40 * traversal values. In conclusion, three things are necessary
41 * to calculate the next traversal value:
43 * (1) the traversal value of the parent
44 * (2) the quadrant of the current node
45 * (3) the prefix of the current node
47 * If we visualize them on our simplified drawing (see the drawing above);
48 * transferred boundaries of (1) would be the outer axis, relevant part
49 * of (2) would be the up right part of the other axis, and (3) would be
52 * For example, consider the case of overlapping. When recursion
53 * descends deeper and deeper down the tree, all quadrants in
54 * the current node will be checked for overlapping. The boundaries
55 * will be re-calculated for all quadrants. Overlap check answers
56 * the question: can any box from this quadrant overlap with the given
57 * box? If yes, then this quadrant will be walked. If no, then this
58 * quadrant will be skipped.
60 * This method provides restrictions for minimum and maximum values of
61 * every dimension of every corner of the box on every level of the tree
62 * except the root. For the root node, we are setting the boundaries
63 * that we don't yet have as infinity.
65 * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
66 * Portions Copyright (c) 1994, Regents of the University of California
69 * src/backend/utils/adt/geo_spgist.c
71 *-------------------------------------------------------------------------
76 #include "access/spgist.h"
77 #include "access/stratnum.h"
78 #include "catalog/pg_type.h"
79 #include "utils/builtins.h"
80 #include "utils/geo_decls.h"
83 * Comparator for qsort
85 * We don't need to use the floating point macros in here, because this
86 * is going only going to be used in a place to effect the performance
87 * of the index, not the correctness.
90 compareDoubles(const void *a, const void *b)
92 double x = *(double *) a;
93 double y = *(double *) b;
97 return (x > y) ? 1 : -1;
114 RangeBox range_box_x;
115 RangeBox range_box_y;
119 * Calculate the quadrant
121 * The quadrant is 8 bit unsigned integer with 4 least bits in use.
122 * This function accepts BOXes as input. They are not casted to
123 * RangeBoxes, yet. All 4 bits are set by comparing a corner of the box.
124 * This makes 16 quadrants in total.
127 getQuadrant(BOX *centroid, BOX *inBox)
131 if (inBox->low.x > centroid->low.x)
134 if (inBox->high.x > centroid->high.x)
137 if (inBox->low.y > centroid->low.y)
140 if (inBox->high.y > centroid->high.y)
147 * Get RangeBox using BOX
149 * We are turning the BOX to our structures to emphasize their function
150 * of representing points in 4D space. It also is more convenient to
151 * access the values with this structure.
154 getRangeBox(BOX *box)
156 RangeBox *range_box = (RangeBox *) palloc(sizeof(RangeBox));
158 range_box->left.low = box->low.x;
159 range_box->left.high = box->high.x;
161 range_box->right.low = box->low.y;
162 range_box->right.high = box->high.y;
168 * Initialize the traversal value
170 * In the beginning, we don't have any restrictions. We have to
171 * initialize the struct to cover the whole 4D space.
176 RectBox *rect_box = (RectBox *) palloc(sizeof(RectBox));
177 double infinity = get_float8_infinity();
179 rect_box->range_box_x.left.low = -infinity;
180 rect_box->range_box_x.left.high = infinity;
182 rect_box->range_box_x.right.low = -infinity;
183 rect_box->range_box_x.right.high = infinity;
185 rect_box->range_box_y.left.low = -infinity;
186 rect_box->range_box_y.left.high = infinity;
188 rect_box->range_box_y.right.low = -infinity;
189 rect_box->range_box_y.right.high = infinity;
195 * Calculate the next traversal value
197 * All centroids are bounded by RectBox, but SP-GiST only keeps
198 * boxes. When we are traversing the tree, we must calculate RectBox,
199 * using centroid and quadrant.
202 nextRectBox(RectBox *rect_box, RangeBox *centroid, uint8 quadrant)
204 RectBox *next_rect_box = (RectBox *) palloc(sizeof(RectBox));
206 memcpy(next_rect_box, rect_box, sizeof(RectBox));
209 next_rect_box->range_box_x.left.low = centroid->left.low;
211 next_rect_box->range_box_x.left.high = centroid->left.low;
214 next_rect_box->range_box_x.right.low = centroid->left.high;
216 next_rect_box->range_box_x.right.high = centroid->left.high;
219 next_rect_box->range_box_y.left.low = centroid->right.low;
221 next_rect_box->range_box_y.left.high = centroid->right.low;
224 next_rect_box->range_box_y.right.low = centroid->right.high;
226 next_rect_box->range_box_y.right.high = centroid->right.high;
228 return next_rect_box;
231 /* Can any range from range_box overlap with this argument? */
233 overlap2D(RangeBox *range_box, Range *query)
235 return FPge(range_box->right.high, query->low) &&
236 FPle(range_box->left.low, query->high);
239 /* Can any rectangle from rect_box overlap with this argument? */
241 overlap4D(RectBox *rect_box, RangeBox *query)
243 return overlap2D(&rect_box->range_box_x, &query->left) &&
244 overlap2D(&rect_box->range_box_y, &query->right);
247 /* Can any range from range_box contain this argument? */
249 contain2D(RangeBox *range_box, Range *query)
251 return FPge(range_box->right.high, query->high) &&
252 FPle(range_box->left.low, query->low);
255 /* Can any rectangle from rect_box contain this argument? */
257 contain4D(RectBox *rect_box, RangeBox *query)
259 return contain2D(&rect_box->range_box_x, &query->left) &&
260 contain2D(&rect_box->range_box_y, &query->right);
263 /* Can any range from range_box be contained by this argument? */
265 contained2D(RangeBox *range_box, Range *query)
267 return FPle(range_box->left.low, query->high) &&
268 FPge(range_box->left.high, query->low) &&
269 FPle(range_box->right.low, query->high) &&
270 FPge(range_box->right.high, query->low);
273 /* Can any rectangle from rect_box be contained by this argument? */
275 contained4D(RectBox *rect_box, RangeBox *query)
277 return contained2D(&rect_box->range_box_x, &query->left) &&
278 contained2D(&rect_box->range_box_y, &query->right);
281 /* Can any range from range_box to be lower than this argument? */
283 lower2D(RangeBox *range_box, Range *query)
285 return FPlt(range_box->left.low, query->low) &&
286 FPlt(range_box->right.low, query->low);
289 /* Can any range from range_box not extend to the right side of the query? */
291 overLower2D(RangeBox *range_box, Range *query)
293 return FPle(range_box->left.low, query->high) &&
294 FPle(range_box->right.low, query->high);
297 /* Can any range from range_box to be higher than this argument? */
299 higher2D(RangeBox *range_box, Range *query)
301 return FPgt(range_box->left.high, query->high) &&
302 FPgt(range_box->right.high, query->high);
305 /* Can any range from range_box not extend to the left side of the query? */
307 overHigher2D(RangeBox *range_box, Range *query)
309 return FPge(range_box->left.high, query->low) &&
310 FPge(range_box->right.high, query->low);
313 /* Can any rectangle from rect_box be left of this argument? */
315 left4D(RectBox *rect_box, RangeBox *query)
317 return lower2D(&rect_box->range_box_x, &query->left);
320 /* Can any rectangle from rect_box does not extend the right of this argument? */
322 overLeft4D(RectBox *rect_box, RangeBox *query)
324 return overLower2D(&rect_box->range_box_x, &query->left);
327 /* Can any rectangle from rect_box be right of this argument? */
329 right4D(RectBox *rect_box, RangeBox *query)
331 return higher2D(&rect_box->range_box_x, &query->left);
334 /* Can any rectangle from rect_box does not extend the left of this argument? */
336 overRight4D(RectBox *rect_box, RangeBox *query)
338 return overHigher2D(&rect_box->range_box_x, &query->left);
341 /* Can any rectangle from rect_box be below of this argument? */
343 below4D(RectBox *rect_box, RangeBox *query)
345 return lower2D(&rect_box->range_box_y, &query->right);
348 /* Can any rectangle from rect_box does not extend above this argument? */
350 overBelow4D(RectBox *rect_box, RangeBox *query)
352 return overLower2D(&rect_box->range_box_y, &query->right);
355 /* Can any rectangle from rect_box be above of this argument? */
357 above4D(RectBox *rect_box, RangeBox *query)
359 return higher2D(&rect_box->range_box_y, &query->right);
362 /* Can any rectangle from rect_box does not extend below of this argument? */
364 overAbove4D(RectBox *rect_box, RangeBox *query)
366 return overHigher2D(&rect_box->range_box_y, &query->right);
370 * SP-GiST config function
373 spg_box_quad_config(PG_FUNCTION_ARGS)
375 spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
377 cfg->prefixType = BOXOID;
378 cfg->labelType = VOIDOID; /* We don't need node labels. */
379 cfg->canReturnData = true;
380 cfg->longValuesOK = false;
386 * SP-GiST choose function
389 spg_box_quad_choose(PG_FUNCTION_ARGS)
391 spgChooseIn *in = (spgChooseIn *) PG_GETARG_POINTER(0);
392 spgChooseOut *out = (spgChooseOut *) PG_GETARG_POINTER(1);
393 BOX *centroid = DatumGetBoxP(in->prefixDatum),
394 *box = DatumGetBoxP(in->leafDatum);
396 out->resultType = spgMatchNode;
397 out->result.matchNode.restDatum = BoxPGetDatum(box);
399 /* nodeN will be set by core, when allTheSame. */
401 out->result.matchNode.nodeN = getQuadrant(centroid, box);
407 * SP-GiST pick-split function
409 * It splits a list of boxes into quadrants by choosing a central 4D
410 * point as the median of the coordinates of the boxes.
413 spg_box_quad_picksplit(PG_FUNCTION_ARGS)
415 spgPickSplitIn *in = (spgPickSplitIn *) PG_GETARG_POINTER(0);
416 spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1);
420 double *lowXs = palloc(sizeof(double) * in->nTuples);
421 double *highXs = palloc(sizeof(double) * in->nTuples);
422 double *lowYs = palloc(sizeof(double) * in->nTuples);
423 double *highYs = palloc(sizeof(double) * in->nTuples);
425 /* Calculate median of all 4D coordinates */
426 for (i = 0; i < in->nTuples; i++)
428 BOX *box = DatumGetBoxP(in->datums[i]);
430 lowXs[i] = box->low.x;
431 highXs[i] = box->high.x;
432 lowYs[i] = box->low.y;
433 highYs[i] = box->high.y;
436 qsort(lowXs, in->nTuples, sizeof(double), compareDoubles);
437 qsort(highXs, in->nTuples, sizeof(double), compareDoubles);
438 qsort(lowYs, in->nTuples, sizeof(double), compareDoubles);
439 qsort(highYs, in->nTuples, sizeof(double), compareDoubles);
441 median = in->nTuples / 2;
443 centroid = palloc(sizeof(BOX));
445 centroid->low.x = lowXs[median];
446 centroid->high.x = highXs[median];
447 centroid->low.y = lowYs[median];
448 centroid->high.y = highYs[median];
450 /* Fill the output */
451 out->hasPrefix = true;
452 out->prefixDatum = BoxPGetDatum(centroid);
455 out->nodeLabels = NULL; /* We don't need node labels. */
457 out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
458 out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
461 * Assign ranges to corresponding nodes according to quadrants relative to
462 * the "centroid" range
464 for (i = 0; i < in->nTuples; i++)
466 BOX *box = DatumGetBoxP(in->datums[i]);
467 uint8 quadrant = getQuadrant(centroid, box);
469 out->leafTupleDatums[i] = BoxPGetDatum(box);
470 out->mapTuplesToNodes[i] = quadrant;
477 * Check if result of consistent method based on bounding box is exact.
480 is_bounding_box_test_exact(StrategyNumber strategy)
484 case RTLeftStrategyNumber:
485 case RTOverLeftStrategyNumber:
486 case RTOverRightStrategyNumber:
487 case RTRightStrategyNumber:
488 case RTOverBelowStrategyNumber:
489 case RTBelowStrategyNumber:
490 case RTAboveStrategyNumber:
491 case RTOverAboveStrategyNumber:
500 * Get bounding box for ScanKey.
503 spg_box_quad_get_scankey_bbox(ScanKey sk, bool *recheck)
505 switch (sk->sk_subtype)
508 return DatumGetBoxP(sk->sk_argument);
511 if (recheck && !is_bounding_box_test_exact(sk->sk_strategy))
513 return &DatumGetPolygonP(sk->sk_argument)->boundbox;
516 elog(ERROR, "unrecognized scankey subtype: %d", sk->sk_subtype);
522 * SP-GiST inner consistent function
525 spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
527 spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
528 spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
530 MemoryContext old_ctx;
538 /* Report that all nodes should be visited */
539 out->nNodes = in->nNodes;
540 out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
541 for (i = 0; i < in->nNodes; i++)
542 out->nodeNumbers[i] = i;
548 * We are saving the traversal value or initialize it an unbounded one, if
549 * we have just begun to walk the tree.
551 if (in->traversalValue)
552 rect_box = in->traversalValue;
554 rect_box = initRectBox();
557 * We are casting the prefix and queries to RangeBoxes for ease of the
558 * following operations.
560 centroid = getRangeBox(DatumGetBoxP(in->prefixDatum));
561 queries = (RangeBox **) palloc(in->nkeys * sizeof(RangeBox *));
562 for (i = 0; i < in->nkeys; i++)
564 BOX *box = spg_box_quad_get_scankey_bbox(&in->scankeys[i], NULL);
566 queries[i] = getRangeBox(box);
569 /* Allocate enough memory for nodes */
571 out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
572 out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes);
575 * We switch memory context, because we want to allocate memory for new
576 * traversal values (next_rect_box) and pass these pieces of memory to
577 * further call of this function.
579 old_ctx = MemoryContextSwitchTo(in->traversalMemoryContext);
581 for (quadrant = 0; quadrant < in->nNodes; quadrant++)
583 RectBox *next_rect_box = nextRectBox(rect_box, centroid, quadrant);
586 for (i = 0; i < in->nkeys; i++)
588 StrategyNumber strategy = in->scankeys[i].sk_strategy;
592 case RTOverlapStrategyNumber:
593 flag = overlap4D(next_rect_box, queries[i]);
596 case RTContainsStrategyNumber:
597 flag = contain4D(next_rect_box, queries[i]);
600 case RTSameStrategyNumber:
601 case RTContainedByStrategyNumber:
602 flag = contained4D(next_rect_box, queries[i]);
605 case RTLeftStrategyNumber:
606 flag = left4D(next_rect_box, queries[i]);
609 case RTOverLeftStrategyNumber:
610 flag = overLeft4D(next_rect_box, queries[i]);
613 case RTRightStrategyNumber:
614 flag = right4D(next_rect_box, queries[i]);
617 case RTOverRightStrategyNumber:
618 flag = overRight4D(next_rect_box, queries[i]);
621 case RTAboveStrategyNumber:
622 flag = above4D(next_rect_box, queries[i]);
625 case RTOverAboveStrategyNumber:
626 flag = overAbove4D(next_rect_box, queries[i]);
629 case RTBelowStrategyNumber:
630 flag = below4D(next_rect_box, queries[i]);
633 case RTOverBelowStrategyNumber:
634 flag = overBelow4D(next_rect_box, queries[i]);
638 elog(ERROR, "unrecognized strategy: %d", strategy);
641 /* If any check is failed, we have found our answer. */
648 out->traversalValues[out->nNodes] = next_rect_box;
649 out->nodeNumbers[out->nNodes] = quadrant;
655 * If this node is not selected, we don't need to keep the next
656 * traversal value in the memory context.
658 pfree(next_rect_box);
663 MemoryContextSwitchTo(old_ctx);
669 * SP-GiST inner consistent function
672 spg_box_quad_leaf_consistent(PG_FUNCTION_ARGS)
674 spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0);
675 spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1);
676 Datum leaf = in->leafDatum;
680 /* All tests are exact. */
681 out->recheck = false;
683 /* leafDatum is what it is... */
684 out->leafValue = in->leafDatum;
686 /* Perform the required comparison(s) */
687 for (i = 0; i < in->nkeys; i++)
689 StrategyNumber strategy = in->scankeys[i].sk_strategy;
690 BOX *box = spg_box_quad_get_scankey_bbox(&in->scankeys[i],
692 Datum query = BoxPGetDatum(box);
696 case RTOverlapStrategyNumber:
697 flag = DatumGetBool(DirectFunctionCall2(box_overlap, leaf,
701 case RTContainsStrategyNumber:
702 flag = DatumGetBool(DirectFunctionCall2(box_contain, leaf,
706 case RTContainedByStrategyNumber:
707 flag = DatumGetBool(DirectFunctionCall2(box_contained, leaf,
711 case RTSameStrategyNumber:
712 flag = DatumGetBool(DirectFunctionCall2(box_same, leaf,
716 case RTLeftStrategyNumber:
717 flag = DatumGetBool(DirectFunctionCall2(box_left, leaf,
721 case RTOverLeftStrategyNumber:
722 flag = DatumGetBool(DirectFunctionCall2(box_overleft, leaf,
726 case RTRightStrategyNumber:
727 flag = DatumGetBool(DirectFunctionCall2(box_right, leaf,
731 case RTOverRightStrategyNumber:
732 flag = DatumGetBool(DirectFunctionCall2(box_overright, leaf,
736 case RTAboveStrategyNumber:
737 flag = DatumGetBool(DirectFunctionCall2(box_above, leaf,
741 case RTOverAboveStrategyNumber:
742 flag = DatumGetBool(DirectFunctionCall2(box_overabove, leaf,
746 case RTBelowStrategyNumber:
747 flag = DatumGetBool(DirectFunctionCall2(box_below, leaf,
751 case RTOverBelowStrategyNumber:
752 flag = DatumGetBool(DirectFunctionCall2(box_overbelow, leaf,
757 elog(ERROR, "unrecognized strategy: %d", strategy);
760 /* If any check is failed, we have found our answer. */
765 PG_RETURN_BOOL(flag);
770 * SP-GiST config function for 2-D types that are lossy represented by their
774 spg_bbox_quad_config(PG_FUNCTION_ARGS)
776 spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
778 cfg->prefixType = BOXOID; /* A type represented by its bounding box */
779 cfg->labelType = VOIDOID; /* We don't need node labels. */
780 cfg->leafType = BOXOID;
781 cfg->canReturnData = false;
782 cfg->longValuesOK = false;
788 * SP-GiST compress function for polygons
791 spg_poly_quad_compress(PG_FUNCTION_ARGS)
793 POLYGON *polygon = PG_GETARG_POLYGON_P(0);
796 box = (BOX *) palloc(sizeof(BOX));
797 *box = polygon->boundbox;
799 PG_RETURN_BOX_P(box);