PostGIS  3.0.6dev-r@@SVN_REVISION@@
gserialized_spgist_nd.c
Go to the documentation of this file.
1 /*****************************************************************************
2  *
3  * gserialized_spgist_nd.c
4  * SP-GiST implementation of 2N-dimensional oct-tree over GIDX boxes
5  *
6  * This module provides SP-GiST implementation for N-dimensional boxes using
7  * oct-tree analogy in 2N-dimensional space. SP-GiST doesn't allow indexing
8  * of overlapping objects. We make N-dimensional objects never-overlapping
9  * in 2N-dimensional space. This technique has some benefits compared to
10  * traditional R-Tree which is implemented as GiST. The performance tests
11  * reveal that this technique especially beneficial with too much overlapping
12  * objects, so called "spaghetti data".
13  *
14  * Unlike the original oct-tree, we are splitting the tree 2N times in
15  * N-dimensional space. For example, if N = 3 then it is easier to imagine
16  * it as splitting space three times into 3:
17  *
18  * | | | |
19  * | | | |
20  * | -----+----- | -----+-----
21  * | | | |
22  * | | | |
23  * -------------+------------- -+- -------------+-------------
24  * | |
25  * | |
26  * | |
27  * | |
28  * | |
29  * FRONT BACK
30  *
31  * We are using GIDX data type as the prefix, but we are treating them
32  * as points in 2N-dimensional space, because GIDX boxes are not enough
33  * to represent the octant boundaries in ND space. They however are
34  * sufficient to point out the additional boundaries of the next
35  * octant.
36  *
37  * We are using traversal values provided by SP-GiST to calculate and
38  * to store the bounds of the octants, while traversing into the tree.
39  * Traversal value has all the boundaries in the ND space, and is
40  * capable of transferring the required boundaries to the following
41  * traversal values. In conclusion, three things are necessary
42  * to calculate the next traversal value:
43  *
44  * (1) the traversal value of the parent
45  * (2) the octant of the current node
46  * (3) the prefix of the current node
47  *
48  * If we visualize them on our simplified drawing (see the drawing above);
49  * transferred boundaries of (1) would be the outer axis, relevant part
50  * of (2) would be the up range_y part of the other axis, and (3) would be
51  * the inner axis.
52  *
53  * For example, consider the case of overlapping. When recursion
54  * descends deeper and deeper down the tree, all octants in
55  * the current node will be checked for overlapping. The boundaries
56  * will be re-calculated for all octants. Overlap check answers
57  * the question: can any box from this octant overlap with the given
58  * box? If yes, then this octant will be walked. If no, then this
59  * octant will be skipped.
60  *
61  * This method provides restrictions for minimum and maximum values of
62  * every dimension of every corner of the box on every level of the tree
63  * except the root. For the root node, we are setting the boundaries
64  * that we don't yet have as infinity.
65  *
66  * Portions Copyright (c) 2018, Esteban Zimanyi, Arthur Lesuisse,
67  * Université Libre de Bruxelles
68  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
69  * Portions Copyright (c) 1994, Regents of the University of California
70  *
71  *****************************************************************************/
72 
73 #include "gserialized_spgist_3d.h"
74 #include "lwgeom_pg.h"
75 #include "gserialized_gist.h"
76 
77 /* Maximum number of children nodes given that GIDX_MAX_DIM = 4 */
78 #define GIDX_MAX_NODES 256
79 
80 /*
81  * ND SP-GiST prototypes
82  */
83 
84 Datum gserialized_spgist_config_nd(PG_FUNCTION_ARGS);
85 Datum gserialized_spgist_choose_nd(PG_FUNCTION_ARGS);
86 Datum gserialized_spgist_picksplit_nd(PG_FUNCTION_ARGS);
87 Datum gserialized_spgist_inner_consistent_nd(PG_FUNCTION_ARGS);
88 Datum gserialized_spgist_leaf_consistent_nd(PG_FUNCTION_ARGS);
89 Datum gserialized_spgist_compress_nd(PG_FUNCTION_ARGS);
90 
91 /* Structure storing the n-dimensional bounding box */
92 
93 typedef struct
94 {
95  GIDX *left;
96  GIDX *right;
97 } CubeGIDX;
98 
99 /*
100  * Comparator for qsort
101  *
102  * We don't need to use the floating point macros in here, because this
103  * is only going to be used in a place to effect the performance
104  * of the index, not the correctness.
105  */
106 static int
107 compareFloats(const void *a, const void *b)
108 {
109  float x = *(float *)a;
110  float y = *(float *)b;
111 
112  if (x == y)
113  return 0;
114  return (x > y) ? 1 : -1;
115 }
116 
117 /*
118  * Calculate the octant
119  *
120  * The octant is a 16-bit unsigned integer with 8 bits in use. The function
121  * accepts 2 GIDX as input. The 8 bits are set by comparing a corner of the
122  * box. This makes 256 octants in total.
123  */
124 static uint16_t
125 getOctant(GIDX *centroid, GIDX *inBox)
126 {
127  uint16_t octant = 0, dim = 0x01;
128  int ndims, i;
129 
130  ndims = Min(GIDX_NDIMS(centroid), GIDX_NDIMS(inBox));
131 
132  for (i = 0; i < ndims; i++)
133  {
134  /* If the missing dimension was not padded with -+FLT_MAX */
135  if (GIDX_GET_MAX(centroid, i) != FLT_MAX && GIDX_GET_MAX(inBox, i) != FLT_MAX)
136  {
137  if (GIDX_GET_MAX(inBox, i) > GIDX_GET_MAX(centroid, i))
138  octant |= dim;
139  dim = dim << 1;
140  if (GIDX_GET_MIN(inBox, i) > GIDX_GET_MIN(centroid, i))
141  octant |= dim;
142  dim = dim << 1;
143  }
144  }
145 
146  return octant;
147 }
148 
149 /*
150  * Initialize the traversal value
151  *
152  * In the beginning, we don't have any restrictions. We have to
153  * initialize the struct to cover the whole ND space.
154  */
155 static CubeGIDX *
156 initCubeBox(int ndims)
157 {
158  CubeGIDX *cube_box = (CubeGIDX *)palloc(sizeof(CubeGIDX));
159  GIDX *left = (GIDX *)palloc(GIDX_SIZE(ndims));
160  GIDX *right = (GIDX *)palloc(GIDX_SIZE(ndims));
161  int i;
162 
163  SET_VARSIZE(left, GIDX_SIZE(ndims));
164  SET_VARSIZE(right, GIDX_SIZE(ndims));
165  cube_box->left = left;
166  cube_box->right = right;
167 
168  for (i = 0; i < ndims; i++)
169  {
170  GIDX_SET_MIN(cube_box->left, i, -1 * FLT_MAX);
171  GIDX_SET_MAX(cube_box->left, i, FLT_MAX);
172  GIDX_SET_MIN(cube_box->right, i, -1 * FLT_MAX);
173  GIDX_SET_MAX(cube_box->right, i, FLT_MAX);
174  }
175 
176  return cube_box;
177 }
178 
179 /*
180  * Calculate the next traversal value
181  *
182  * All centroids are bounded by CubeGIDX, but SP-GiST only keeps
183  * boxes. When we are traversing the tree, we must calculate CubeGIDX,
184  * using centroid and octant.
185  */
186 static CubeGIDX *
187 nextCubeBox(CubeGIDX *cube_box, GIDX *centroid, uint16_t octant)
188 {
189  int ndims = GIDX_NDIMS(centroid), i;
190  CubeGIDX *next_cube_box = (CubeGIDX *)palloc(sizeof(CubeGIDX));
191  GIDX *left = (GIDX *)palloc(GIDX_SIZE(ndims));
192  GIDX *right = (GIDX *)palloc(GIDX_SIZE(ndims));
193  uint16_t dim = 0x01;
194 
195  memcpy(left, cube_box->left, VARSIZE(cube_box->left));
196  memcpy(right, cube_box->right, VARSIZE(cube_box->right));
197  next_cube_box->left = left;
198  next_cube_box->right = right;
199 
200  for (i = 0; i < ndims; i++)
201  {
202  if (GIDX_GET_MAX(cube_box->left, i) != FLT_MAX && GIDX_GET_MAX(centroid, i) != FLT_MAX)
203  {
204  if (octant & dim)
205  GIDX_GET_MIN(next_cube_box->right, i) = GIDX_GET_MAX(centroid, i);
206  else
207  GIDX_GET_MAX(next_cube_box->right, i) = GIDX_GET_MAX(centroid, i);
208 
209  dim = dim << 1;
210 
211  if (octant & dim)
212  GIDX_GET_MIN(next_cube_box->left, i) = GIDX_GET_MIN(centroid, i);
213  else
214  GIDX_GET_MAX(next_cube_box->left, i) = GIDX_GET_MIN(centroid, i);
215 
216  dim = dim << 1;
217  }
218  }
219 
220  return next_cube_box;
221 }
222 
223 /* Can any cube from cube_box overlap with query? */
224 static bool
225 overlapND(CubeGIDX *cube_box, GIDX *query)
226 {
227  int i, ndims;
228  bool result = true;
229 
230  ndims = Min(GIDX_NDIMS(cube_box->left), GIDX_NDIMS(query));
231 
232  for (i = 0; i < ndims; i++)
233  {
234  /* If the missing dimension was not padded with -+FLT_MAX */
235  if (GIDX_GET_MAX(cube_box->left, i) != FLT_MAX && GIDX_GET_MAX(query, i) != FLT_MAX)
236  result &= (GIDX_GET_MIN(cube_box->left, i) <= GIDX_GET_MAX(query, i)) &&
237  (GIDX_GET_MAX(cube_box->right, i) >= GIDX_GET_MIN(query, i));
238  }
239  return result;
240 }
241 
242 /* Can any cube from cube_box contain query? */
243 static bool
244 containND(CubeGIDX *cube_box, GIDX *query)
245 {
246  int i, ndims;
247  bool result = true;
248 
249  ndims = Min(GIDX_NDIMS(cube_box->left), GIDX_NDIMS(query));
250 
251  for (i = 0; i < ndims; i++)
252  {
253  /* If the missing dimension was not padded with -+FLT_MAX */
254  if (GIDX_GET_MAX(cube_box->left, i) != FLT_MAX && GIDX_GET_MAX(query, i) != FLT_MAX)
255  result &= (GIDX_GET_MAX(cube_box->right, i) >= GIDX_GET_MAX(query, i)) &&
256  (GIDX_GET_MIN(cube_box->left, i) <= GIDX_GET_MIN(query, i));
257  }
258  return result;
259 }
260 
261 /*
262  * SP-GiST config function
263  */
264 
266 
267 PGDLLEXPORT Datum gserialized_spgist_config_nd(PG_FUNCTION_ARGS)
268 {
269  spgConfigOut *cfg = (spgConfigOut *)PG_GETARG_POINTER(1);
270  Oid boxoid = InvalidOid;
271  postgis_initialize_cache(fcinfo);
272  boxoid = postgis_oid(GIDXOID);
273 
274  cfg->prefixType = boxoid;
275  cfg->labelType = VOIDOID; /* We don't need node labels. */
276  cfg->leafType = boxoid;
277  cfg->canReturnData = false;
278  cfg->longValuesOK = false;
279 
280  PG_RETURN_VOID();
281 }
282 
283 /*
284  * SP-GiST choose function
285  */
286 
288 
289 PGDLLEXPORT Datum gserialized_spgist_choose_nd(PG_FUNCTION_ARGS)
290 {
291  spgChooseIn *in = (spgChooseIn *)PG_GETARG_POINTER(0);
292  spgChooseOut *out = (spgChooseOut *)PG_GETARG_POINTER(1);
293  GIDX *centroid = (GIDX *)DatumGetPointer(in->prefixDatum), *box = (GIDX *)DatumGetPointer(in->leafDatum);
294 
295  out->resultType = spgMatchNode;
296  out->result.matchNode.restDatum = PointerGetDatum(box);
297 
298  /* nodeN will be set by core, when allTheSame. */
299  if (!in->allTheSame)
300  out->result.matchNode.nodeN = getOctant(centroid, box);
301 
302  PG_RETURN_VOID();
303 }
304 
305 /*
306  * SP-GiST pick-split function
307  *
308  * It splits a list of boxes into octants by choosing a central ND
309  * point as the median of the coordinates of the boxes.
310  */
312 
313 PGDLLEXPORT Datum gserialized_spgist_picksplit_nd(PG_FUNCTION_ARGS)
314 {
315  spgPickSplitIn *in = (spgPickSplitIn *)PG_GETARG_POINTER(0);
316  spgPickSplitOut *out = (spgPickSplitOut *)PG_GETARG_POINTER(1);
317  GIDX *box, *centroid;
318  float *lowXs, *highXs;
319  int ndims, maxdims = -1, count[GIDX_MAX_DIM], median, dim, tuple;
320 
321  for (dim = 0; dim < GIDX_MAX_DIM; dim++)
322  count[dim] = 0;
323 
324  lowXs = palloc(sizeof(float) * in->nTuples * GIDX_MAX_DIM),
325  highXs = palloc(sizeof(float) * in->nTuples * GIDX_MAX_DIM);
326 
327  /* Calculate maxdims median of all ND coordinates */
328  for (tuple = 0; tuple < in->nTuples; tuple++)
329  {
330  box = (GIDX *)DatumGetPointer(in->datums[tuple]);
331  ndims = GIDX_NDIMS(box);
332  if (maxdims < ndims)
333  maxdims = ndims;
334  for (dim = 0; dim < ndims; dim++)
335  {
336  /* If the missing dimension was not padded with -+FLT_MAX */
337  if (GIDX_GET_MAX(box, dim) != FLT_MAX)
338  {
339  lowXs[dim * in->nTuples + count[dim]] = GIDX_GET_MIN(box, dim);
340  highXs[dim * in->nTuples + count[dim]] = GIDX_GET_MAX(box, dim);
341  count[dim]++;
342  }
343  }
344  }
345 
346  for (dim = 0; dim < maxdims; dim++)
347  {
348  qsort(&lowXs[dim * in->nTuples], count[dim], sizeof(float), compareFloats);
349  qsort(&highXs[dim * in->nTuples], count[dim], sizeof(float), compareFloats);
350  }
351 
352  centroid = (GIDX *)palloc(GIDX_SIZE(maxdims));
353  SET_VARSIZE(centroid, GIDX_SIZE(maxdims));
354 
355  for (dim = 0; dim < maxdims; dim++)
356  {
357  median = count[dim] / 2;
358  GIDX_SET_MIN(centroid, dim, lowXs[dim * in->nTuples + median]);
359  GIDX_SET_MAX(centroid, dim, highXs[dim * in->nTuples + median]);
360  }
361 
362  /* Fill the output */
363  out->hasPrefix = true;
364  out->prefixDatum = PointerGetDatum(gidx_copy(centroid));
365 
366  out->nNodes = 0x01 << (2 * maxdims);
367  out->nodeLabels = NULL; /* We don't need node labels. */
368 
369  out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
370  out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
371 
372  /*
373  * Assign ranges to corresponding nodes according to octants relative to
374  * the "centroid" range
375  */
376  for (tuple = 0; tuple < in->nTuples; tuple++)
377  {
378  GIDX *box = (GIDX *)DatumGetPointer(in->datums[tuple]);
379  uint16_t octant = getOctant(centroid, box);
380 
381  out->leafTupleDatums[tuple] = PointerGetDatum(box);
382  out->mapTuplesToNodes[tuple] = octant;
383  }
384 
385  pfree(lowXs);
386  pfree(highXs);
387 
388  PG_RETURN_VOID();
389 }
390 
391 /*
392  * SP-GiST inner consistent function
393  */
395 
396 PGDLLEXPORT Datum gserialized_spgist_inner_consistent_nd(PG_FUNCTION_ARGS)
397 {
398  spgInnerConsistentIn *in = (spgInnerConsistentIn *)PG_GETARG_POINTER(0);
399  spgInnerConsistentOut *out = (spgInnerConsistentOut *)PG_GETARG_POINTER(1);
400  MemoryContext old_ctx;
401  CubeGIDX *cube_box;
402  int *nodeNumbers, i, j;
403  void **traversalValues;
404  char gidxmem[GIDX_MAX_SIZE];
405  GIDX *centroid, *query_gbox_index = (GIDX *)gidxmem;
406 
407  POSTGIS_DEBUG(4, "[SPGIST] 'inner consistent' function called");
408 
409  if (in->allTheSame)
410  {
411  /* Report that all nodes should be visited */
412  out->nNodes = in->nNodes;
413  out->nodeNumbers = (int *)palloc(sizeof(int) * in->nNodes);
414  for (i = 0; i < in->nNodes; i++)
415  out->nodeNumbers[i] = i;
416 
417  PG_RETURN_VOID();
418  }
419 
420  /*
421  * We switch memory context, because we want to allocate memory for new
422  * traversal values (next_cube_box) and pass these pieces of memory to
423  * further call of this function.
424  */
425  old_ctx = MemoryContextSwitchTo(in->traversalMemoryContext);
426 
427  centroid = (GIDX *)DatumGetPointer(in->prefixDatum);
428 
429  /*
430  * We are saving the traversal value or initialize it an unbounded one, if
431  * we have just begun to walk the tree.
432  */
433  if (in->traversalValue)
434  cube_box = in->traversalValue;
435  else
436  cube_box = initCubeBox(GIDX_NDIMS(centroid));
437 
438  /* Allocate enough memory for nodes */
439  out->nNodes = 0;
440  nodeNumbers = (int *)palloc(sizeof(int) * in->nNodes);
441  traversalValues = (void **)palloc(sizeof(void *) * in->nNodes);
442 
443  for (i = 0; i < in->nNodes; i++)
444  {
445  CubeGIDX *next_cube_box = nextCubeBox(cube_box, centroid, (uint16_t)i);
446  bool flag = true;
447 
448  for (j = 0; j < in->nkeys; j++)
449  {
450  StrategyNumber strategy = in->scankeys[j].sk_strategy;
451  Datum query = in->scankeys[j].sk_argument;
452 
453  /* Quick sanity check on query argument. */
454  if (DatumGetPointer(query) == NULL)
455  {
456  POSTGIS_DEBUG(4, "[SPGIST] null query pointer (!?!)");
457  flag = false;
458  break;
459  }
460 
461  /* Null box should never make this far. */
462  if (gserialized_datum_get_gidx_p(query, query_gbox_index) == LW_FAILURE)
463  {
464  POSTGIS_DEBUG(4, "[SPGIST] null query_gbox_index!");
465  flag = false;
466  break;
467  }
468 
469  switch (strategy)
470  {
473  flag = overlapND(next_cube_box, query_gbox_index);
474  break;
475 
478  flag = containND(next_cube_box, query_gbox_index);
479  break;
480 
481  default:
482  elog(ERROR, "unrecognized strategy: %d", strategy);
483  }
484 
485  /* If any check is failed, we have found our answer. */
486  if (!flag)
487  break;
488  }
489 
490  if (flag)
491  {
492  traversalValues[out->nNodes] = next_cube_box;
493  nodeNumbers[out->nNodes] = i;
494  out->nNodes++;
495  }
496  else
497  {
498  /*
499  * If this node is not selected, we don't need to keep the next
500  * traversal value in the memory context.
501  */
502  pfree(next_cube_box);
503  }
504  }
505 
506  /* Pass to the next level only the values that need to be traversed */
507  out->nodeNumbers = (int *)palloc(sizeof(int) * out->nNodes);
508  out->traversalValues = (void **)palloc(sizeof(void *) * out->nNodes);
509  for (i = 0; i < out->nNodes; i++)
510  {
511  out->nodeNumbers[i] = nodeNumbers[i];
512  out->traversalValues[i] = traversalValues[i];
513  }
514  pfree(nodeNumbers);
515  pfree(traversalValues);
516 
517  /* Switch after */
518  MemoryContextSwitchTo(old_ctx);
519 
520  PG_RETURN_VOID();
521 }
522 
523 /*
524  * SP-GiST inner consistent function
525  */
527 
528 PGDLLEXPORT Datum gserialized_spgist_leaf_consistent_nd(PG_FUNCTION_ARGS)
529 {
530  spgLeafConsistentIn *in = (spgLeafConsistentIn *)PG_GETARG_POINTER(0);
531  spgLeafConsistentOut *out = (spgLeafConsistentOut *)PG_GETARG_POINTER(1);
532  bool flag = true;
533  int i;
534  char gidxmem[GIDX_MAX_SIZE];
535  GIDX *leaf = (GIDX *)DatumGetPointer(in->leafDatum), *query_gbox_index = (GIDX *)gidxmem;
536 
537  POSTGIS_DEBUG(4, "[SPGIST] 'leaf consistent' function called");
538 
539  /* All tests are exact. */
540  out->recheck = false;
541 
542  /* leafDatum is what it is... */
543  out->leafValue = in->leafDatum;
544 
545  /* Perform the required comparison(s) */
546  for (i = 0; i < in->nkeys; i++)
547  {
548  StrategyNumber strategy = in->scankeys[i].sk_strategy;
549  Datum query = in->scankeys[i].sk_argument;
550 
551  /* Quick sanity check on query argument. */
552  if (DatumGetPointer(query) == NULL)
553  {
554  POSTGIS_DEBUG(4, "[SPGIST] null query pointer (!?!)");
555  flag = false;
556  }
557 
558  /* Null box should never make this far. */
559  if (gserialized_datum_get_gidx_p(query, query_gbox_index) == LW_FAILURE)
560  {
561  POSTGIS_DEBUG(4, "[SPGIST] null query_gbox_index!");
562  flag = false;
563  }
564 
565  switch (strategy)
566  {
568  flag = gidx_overlaps(leaf, query_gbox_index);
569  break;
570 
572  flag = gidx_contains(leaf, query_gbox_index);
573  break;
574 
576  flag = gidx_contains(query_gbox_index, leaf);
577  break;
578 
580  flag = gidx_equals(leaf, query_gbox_index);
581  break;
582 
583  default:
584  elog(ERROR, "unrecognized strategy: %d", strategy);
585  }
586 
587  /* If any check is failed, we have found our answer. */
588  if (!flag)
589  break;
590  }
591 
592  PG_RETURN_BOOL(flag);
593 }
594 
596 
597 PGDLLEXPORT Datum gserialized_spgist_compress_nd(PG_FUNCTION_ARGS)
598 {
599  char gidxmem[GIDX_MAX_SIZE];
600  GIDX *result = (GIDX *)gidxmem;
601  long unsigned int i;
602 
603  POSTGIS_DEBUG(4, "[SPGIST] 'compress' function called");
604 
605  /* Input entry is null? Return NULL. */
606  if (PG_ARGISNULL(0))
607  {
608  POSTGIS_DEBUG(4, "[SPGIST] null entry (!?!)");
609  PG_RETURN_NULL();
610  }
611 
612  /* Is the bounding box valid (non-empty, non-infinite) ?
613  * If not, return NULL. */
614  if (gserialized_datum_get_gidx_p(PG_GETARG_DATUM(0), result) == LW_FAILURE)
615  {
616  POSTGIS_DEBUG(4, "[SPGIST] empty geometry!");
617  PG_RETURN_NULL();
618  }
619 
620  POSTGIS_DEBUGF(4, "[SPGIST] got GIDX: %s", gidx_to_string(result));
621 
622  /* Check all the dimensions for finite values.
623  * If not, use the "unknown" GIDX as a key */
624  for (i = 0; i < GIDX_NDIMS(result); i++)
625  {
626  if (!isfinite(GIDX_GET_MAX(result, i)) || !isfinite(GIDX_GET_MIN(result, i)))
627  {
628  gidx_set_unknown(result);
629  PG_RETURN_POINTER(result);
630  }
631  }
632 
633  /* Ensure bounding box has minimums below maximums. */
634  gidx_validate(result);
635 
636  /* Return GIDX. */
637  PG_RETURN_POINTER(gidx_copy(result));
638 }
639 
640 /*****************************************************************************/
void gidx_set_unknown(GIDX *a)
GIDX * gidx_copy(GIDX *b)
bool gidx_contains(GIDX *a, GIDX *b)
bool gidx_equals(GIDX *a, GIDX *b)
void gidx_validate(GIDX *b)
bool gidx_overlaps(GIDX *a, GIDX *b)
#define SPGOverlapStrategyNumber
#define SPGSameStrategyNumber
#define SPGContainedByStrategyNumber
#define SPGContainsStrategyNumber
static bool overlapND(CubeGIDX *cube_box, GIDX *query)
Datum gserialized_spgist_compress_nd(PG_FUNCTION_ARGS)
PG_FUNCTION_INFO_V1(gserialized_spgist_config_nd)
static CubeGIDX * nextCubeBox(CubeGIDX *cube_box, GIDX *centroid, uint16_t octant)
Datum gserialized_spgist_inner_consistent_nd(PG_FUNCTION_ARGS)
static uint16_t getOctant(GIDX *centroid, GIDX *inBox)
Datum gserialized_spgist_config_nd(PG_FUNCTION_ARGS)
Datum gserialized_spgist_choose_nd(PG_FUNCTION_ARGS)
static CubeGIDX * initCubeBox(int ndims)
Datum gserialized_spgist_picksplit_nd(PG_FUNCTION_ARGS)
static bool containND(CubeGIDX *cube_box, GIDX *query)
static int compareFloats(const void *a, const void *b)
Datum gserialized_spgist_leaf_consistent_nd(PG_FUNCTION_ARGS)
#define LW_FAILURE
Definition: liblwgeom.h:110
int count
Definition: genraster.py:57
Datum centroid(PG_FUNCTION_ARGS)