PostGIS  3.3.9dev-r@@SVN_REVISION@@
lwgeom_geos_cluster.c
Go to the documentation of this file.
1 /**********************************************************************
2  *
3  * PostGIS - Spatial Types for PostgreSQL
4  * http://postgis.net
5  *
6  * PostGIS is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * PostGIS is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
18  *
19  **********************************************************************
20  *
21  * Copyright 2015-2016 Daniel Baston <dbaston@gmail.com>
22  *
23  **********************************************************************/
24 
25 #include <string.h>
26 #include "liblwgeom.h"
27 #include "liblwgeom_internal.h"
28 #include "lwgeom_log.h"
29 #include "lwgeom_geos.h"
30 #include "lwunionfind.h"
31 
32 static const int STRTREE_NODE_CAPACITY = 10;
33 
34 /* Utility struct used to accumulate items in GEOSSTRtree_query callback */
36 {
37  void** items_found;
38  uint32_t items_found_size;
39  uint32_t num_items_found;
40 };
41 
42 /* Utility struct to keep GEOSSTRtree and associated structures to be freed after use */
43 struct STRTree
44 {
45  GEOSSTRtree* tree;
46  GEOSGeometry** envelopes;
47  uint32_t* geom_ids;
48  uint32_t num_geoms;
49 };
50 
51 static struct STRTree make_strtree(void** geoms, uint32_t num_geoms, char is_lwgeom);
52 static void destroy_strtree(struct STRTree * tree);
53 static int union_intersecting_pairs(GEOSGeometry** geoms, uint32_t num_geoms, UNIONFIND* uf);
54 static int combine_geometries(UNIONFIND* uf, void** geoms, uint32_t num_geoms, void*** clustersGeoms, uint32_t* num_clusters, char is_lwgeom);
55 
56 /* Make a minimal GEOSGeometry* whose Envelope covers the same 2D extent as
57  * the supplied GBOX. This is faster and uses less memory than building a
58  * five-point polygon with GBOX2GEOS.
59  */
60 static GEOSGeometry*
62 {
63  if (lwgeom_is_empty(g))
64  return GEOSGeom_createEmptyPolygon();
65 
66  if (lwgeom_get_type(g) == POINTTYPE) {
67  const POINT2D* pt = getPoint2d_cp(lwgeom_as_lwpoint(g)->point, 0);
68  return make_geos_point(pt->x, pt->y);
69  } else {
70  const GBOX* box = lwgeom_get_bbox(g);
71  if (!box)
72  return NULL;
73 
74  return make_geos_segment(box->xmin, box->ymin, box->xmax, box->ymax);
75  }
76 }
77 
80 static struct STRTree
81 make_strtree(void** geoms, uint32_t num_geoms, char is_lwgeom)
82 {
83  struct STRTree tree;
84  tree.envelopes = 0;
85  tree.num_geoms = 0;
86  tree.geom_ids = 0;
87 
88  tree.tree = GEOSSTRtree_create(STRTREE_NODE_CAPACITY);
89  if (tree.tree == NULL)
90  {
91  return tree;
92  }
93  tree.geom_ids = lwalloc(num_geoms * sizeof(uint32_t));
94  tree.num_geoms = num_geoms;
95 
96  if (is_lwgeom)
97  {
98  uint32_t i;
99  tree.envelopes = lwalloc(num_geoms * sizeof(GEOSGeometry*));
100  for (i = 0; i < num_geoms; i++)
101  {
102  tree.geom_ids[i] = i;
103  tree.envelopes[i] = geos_envelope_surrogate(geoms[i]);
104  GEOSSTRtree_insert(tree.tree, tree.envelopes[i], &(tree.geom_ids[i]));
105  }
106  }
107  else
108  {
109  uint32_t i;
110  tree.envelopes = NULL;
111  for (i = 0; i < num_geoms; i++)
112  {
113  tree.geom_ids[i] = i;
114  GEOSSTRtree_insert(tree.tree, geoms[i], &(tree.geom_ids[i]));
115  }
116  }
117 
118  return tree;
119 }
120 
122 static void
124 {
125  size_t i;
126  GEOSSTRtree_destroy(tree->tree);
127 
128  if (tree->envelopes)
129  {
130  for (i = 0; i < tree->num_geoms; i++)
131  {
132  GEOSGeom_destroy(tree->envelopes[i]);
133  }
134  lwfree(tree->envelopes);
135  }
136  lwfree(tree->geom_ids);
137 }
138 
139 static void
140 query_accumulate(void* item, void* userdata)
141 {
142  struct QueryContext *cxt = userdata;
143  if (!cxt->items_found)
144  {
145  cxt->items_found_size = 8;
146  cxt->items_found = lwalloc(cxt->items_found_size * sizeof(void*));
147  }
148 
149  if (cxt->num_items_found >= cxt->items_found_size)
150  {
151  cxt->items_found_size = 2 * cxt->items_found_size;
152  cxt->items_found = lwrealloc(cxt->items_found, cxt->items_found_size * sizeof(void*));
153  }
154  cxt->items_found[cxt->num_items_found++] = item;
155 }
156 
157 /* Identify intersecting geometries and mark them as being in the same set */
158 static int
159 union_intersecting_pairs(GEOSGeometry** geoms, uint32_t num_geoms, UNIONFIND* uf)
160 {
161  uint32_t p, i;
162  struct STRTree tree;
163  struct QueryContext cxt =
164  {
165  .items_found = NULL,
166  .num_items_found = 0,
167  .items_found_size = 0
168  };
169  int success = LW_SUCCESS;
170 
171  if (num_geoms <= 1)
172  return LW_SUCCESS;
173 
174  tree = make_strtree((void**) geoms, num_geoms, LW_FALSE);
175  if (tree.tree == NULL)
176  {
177  destroy_strtree(&tree);
178  return LW_FAILURE;
179  }
180 
181  for (p = 0; p < num_geoms; p++)
182  {
183  const GEOSPreparedGeometry* prep = NULL;
184 
185  if (!geoms[p] || GEOSisEmpty(geoms[p]))
186  continue;
187 
188  cxt.num_items_found = 0;
189  GEOSSTRtree_query(tree.tree, geoms[p], &query_accumulate, &cxt);
190 
191  for (i = 0; i < cxt.num_items_found; i++)
192  {
193  uint32_t q = *((uint32_t*) cxt.items_found[i]);
194 
195  if (p != q && UF_find(uf, p) != UF_find(uf, q))
196  {
197  int geos_type = GEOSGeomTypeId(geoms[p]);
198  int geos_result;
199 
200  /* Don't build prepared a geometry around a Point or MultiPoint -
201  * there are some problems in the implementation, and it's not clear
202  * there would be a performance benefit in any case. (See #3433)
203  */
204  if (geos_type != GEOS_POINT && geos_type != GEOS_MULTIPOINT)
205  {
206  /* Lazy initialize prepared geometry */
207  if (prep == NULL)
208  {
209  prep = GEOSPrepare(geoms[p]);
210  }
211  geos_result = GEOSPreparedIntersects(prep, geoms[q]);
212  }
213  else
214  {
215  geos_result = GEOSIntersects(geoms[p], geoms[q]);
216  }
217  if (geos_result > 1)
218  {
219  success = LW_FAILURE;
220  break;
221  }
222  else if (geos_result)
223  {
224  UF_union(uf, p, q);
225  }
226  }
227  }
228 
229  if (prep)
230  GEOSPreparedGeom_destroy(prep);
231 
232  if (!success)
233  break;
234  }
235 
236  if (cxt.items_found)
237  lwfree(cxt.items_found);
238 
239  destroy_strtree(&tree);
240  return success;
241 }
242 
246 int
247 cluster_intersecting(GEOSGeometry** geoms, uint32_t num_geoms, GEOSGeometry*** clusterGeoms, uint32_t* num_clusters)
248 {
249  int cluster_success;
250  UNIONFIND* uf = UF_create(num_geoms);
251 
252  if (union_intersecting_pairs(geoms, num_geoms, uf) == LW_FAILURE)
253  {
254  UF_destroy(uf);
255  return LW_FAILURE;
256  }
257 
258  cluster_success = combine_geometries(uf, (void**) geoms, num_geoms, (void***) clusterGeoms, num_clusters, 0);
259  UF_destroy(uf);
260  return cluster_success;
261 }
262 
263 static int
264 dbscan_update_context(GEOSSTRtree* tree, struct QueryContext* cxt, LWGEOM** geoms, uint32_t p, double eps)
265 {
266  cxt->num_items_found = 0;
267 
268  GEOSGeometry* query_envelope;
269 
270  LW_ON_INTERRUPT(return LW_FAILURE);
271 
272  if (geoms[p]->type == POINTTYPE)
273  {
274  const POINT2D* pt = getPoint2d_cp(lwgeom_as_lwpoint(geoms[p])->point, 0);
275  query_envelope = make_geos_segment( pt->x - eps, pt->y - eps, pt->x + eps, pt->y + eps );
276  } else {
277  const GBOX* box = lwgeom_get_bbox(geoms[p]);
278  query_envelope = make_geos_segment( box->xmin - eps, box->ymin - eps, box->xmax + eps, box->ymax + eps );
279  }
280 
281  if (!query_envelope)
282  return LW_FAILURE;
283 
284  GEOSSTRtree_query(tree, query_envelope, &query_accumulate, cxt);
285 
286  GEOSGeom_destroy(query_envelope);
287 
288  return LW_SUCCESS;
289 }
290 
291 /* Union p's cluster with q's cluster, if q is not a border point of another cluster.
292  * Applicable to DBSCAN with minpoints > 1.
293  */
294 static void
295 union_if_available(UNIONFIND* uf, uint32_t p, uint32_t q, char* is_in_core, char* in_a_cluster)
296 {
297  if (in_a_cluster[q])
298  {
299  /* Can we merge p's cluster with q's cluster? We can do this only
300  * if both p and q are considered _core_ points of their respective
301  * clusters.
302  */
303  if (is_in_core[q])
304  {
305  UF_union(uf, p, q);
306  }
307  }
308  else
309  {
310  UF_union(uf, p, q);
311  in_a_cluster[q] = LW_TRUE;
312  }
313 }
314 
315 /* An optimized DBSCAN union for the case where min_points == 1.
316  * If min_points == 1, then we don't care how many neighbors we find; we can union clusters
317  * on the fly, as we go through the distance calculations. This potentially allows us
318  * to avoid some distance computations altogether.
319  */
320 static int
321 union_dbscan_minpoints_1(LWGEOM** geoms, uint32_t num_geoms, UNIONFIND* uf, double eps, char** in_a_cluster_ret)
322 {
323  uint32_t p, i;
324  struct STRTree tree;
325  struct QueryContext cxt =
326  {
327  .items_found = NULL,
328  .num_items_found = 0,
329  .items_found_size = 0
330  };
331  int success = LW_SUCCESS;
332 
333  if (in_a_cluster_ret)
334  {
335  char* in_a_cluster = lwalloc(num_geoms * sizeof(char));
336  for (i = 0; i < num_geoms; i++)
337  in_a_cluster[i] = LW_TRUE;
338  *in_a_cluster_ret = in_a_cluster;
339  }
340 
341  if (num_geoms <= 1)
342  return LW_SUCCESS;
343 
344  tree = make_strtree((void**) geoms, num_geoms, LW_TRUE);
345  if (tree.tree == NULL)
346  {
347  destroy_strtree(&tree);
348  return LW_FAILURE;
349  }
350 
351  for (p = 0; p < num_geoms; p++)
352  {
353  int rv = LW_SUCCESS;
354  if (lwgeom_is_empty(geoms[p]))
355  continue;
356 
357  rv = dbscan_update_context(tree.tree, &cxt, geoms, p, eps);
358  if (rv == LW_FAILURE)
359  {
360  destroy_strtree(&tree);
361  return LW_FAILURE;
362  }
363  for (i = 0; i < cxt.num_items_found; i++)
364  {
365  uint32_t q = *((uint32_t*) cxt.items_found[i]);
366 
367  if (UF_find(uf, p) != UF_find(uf, q))
368  {
369  double mindist = lwgeom_mindistance2d_tolerance(geoms[p], geoms[q], eps);
370  if (mindist == FLT_MAX)
371  {
372  success = LW_FAILURE;
373  break;
374  }
375 
376  if (mindist <= eps)
377  UF_union(uf, p, q);
378  }
379  }
380  }
381 
382  if (cxt.items_found)
383  lwfree(cxt.items_found);
384 
385  destroy_strtree(&tree);
386 
387  return success;
388 }
389 
390 static int
391 union_dbscan_general(LWGEOM** geoms, uint32_t num_geoms, UNIONFIND* uf, double eps, uint32_t min_points, char** in_a_cluster_ret)
392 {
393  uint32_t p, i;
394  struct STRTree tree;
395  struct QueryContext cxt =
396  {
397  .items_found = NULL,
398  .num_items_found = 0,
399  .items_found_size = 0
400  };
401  int success = LW_SUCCESS;
402  uint32_t* neighbors;
403  char* in_a_cluster;
404  char* is_in_core;
405 
406  in_a_cluster = lwalloc(num_geoms * sizeof(char));
407  memset(in_a_cluster, 0, num_geoms * sizeof(char));
408 
409  if (in_a_cluster_ret)
410  *in_a_cluster_ret = in_a_cluster;
411 
412  /* Bail if we don't even have enough inputs to make a cluster. */
413  if (num_geoms < min_points)
414  {
415  if (!in_a_cluster_ret)
416  lwfree(in_a_cluster);
417  return LW_SUCCESS;
418  }
419 
420  tree = make_strtree((void**) geoms, num_geoms, LW_TRUE);
421  if (tree.tree == NULL)
422  {
423  destroy_strtree(&tree);
424  return LW_FAILURE;
425  }
426 
427  is_in_core = lwalloc(num_geoms * sizeof(char));
428  memset(is_in_core, 0, num_geoms * sizeof(char));
429  neighbors = lwalloc(min_points * sizeof(uint32_t));
430 
431  for (p = 0; p < num_geoms; p++)
432  {
433  uint32_t num_neighbors = 0;
434  int rv;
435 
436  if (lwgeom_is_empty(geoms[p]))
437  continue;
438 
439  rv = dbscan_update_context(tree.tree, &cxt, geoms, p, eps);
440  if (rv == LW_FAILURE)
441  {
442  destroy_strtree(&tree);
443  return LW_FAILURE;
444  }
445 
446  /* We didn't find enough points to do anything, even if they are all within eps. */
447  if (cxt.num_items_found < min_points)
448  continue;
449 
450  for (i = 0; i < cxt.num_items_found; i++)
451  {
452  uint32_t q = *((uint32_t*) cxt.items_found[i]);
453 
454  if (num_neighbors >= min_points)
455  {
456  /* If we've already identified p as a core point, and it's already
457  * in the same cluster in q, then there's nothing to learn by
458  * computing the distance.
459  */
460  if (UF_find(uf, p) == UF_find(uf, q))
461  continue;
462 
463  /* Similarly, if q is already identifed as a border point of another
464  * cluster, there's no point figuring out what the distance is.
465  */
466  if (in_a_cluster[q] && !is_in_core[q])
467  continue;
468  }
469 
470  double mindist = lwgeom_mindistance2d_tolerance(geoms[p], geoms[q], eps);
471  if (mindist == FLT_MAX)
472  {
473  success = LW_FAILURE;
474  break;
475  }
476 
477  if (mindist <= eps)
478  {
479  /* If we haven't hit min_points yet, we don't know if we can union p and q.
480  * Just set q aside for now.
481  */
482  if (num_neighbors < min_points)
483  {
484  neighbors[num_neighbors++] = q;
485 
486  /* If we just hit min_points, we can now union all of the neighbor geometries
487  * we've been saving.
488  */
489  if (num_neighbors == min_points)
490  {
491  uint32_t j;
492  is_in_core[p] = LW_TRUE;
493  in_a_cluster[p] = LW_TRUE;
494  for (j = 0; j < num_neighbors; j++)
495  {
496  union_if_available(uf, p, neighbors[j], is_in_core, in_a_cluster);
497  }
498  }
499  }
500  else
501  {
502  /* If we're above min_points, no need to store our neighbors, just go ahead
503  * and union them now. This may allow us to cut out some distance
504  * computations.
505  */
506  union_if_available(uf, p, q, is_in_core, in_a_cluster);
507  }
508  }
509  }
510 
511  if (!success)
512  break;
513  }
514 
515  lwfree(neighbors);
516  lwfree(is_in_core);
517 
518  /* Free in_a_cluster if we're not giving it to our caller */
519  if (!in_a_cluster_ret)
520  lwfree(in_a_cluster);
521 
522  if (cxt.items_found)
523  lwfree(cxt.items_found);
524 
525  destroy_strtree(&tree);
526  return success;
527 }
528 
529 int union_dbscan(LWGEOM** geoms, uint32_t num_geoms, UNIONFIND* uf, double eps, uint32_t min_points, char** in_a_cluster_ret)
530 {
531  if (min_points <= 1)
532  return union_dbscan_minpoints_1(geoms, num_geoms, uf, eps, in_a_cluster_ret);
533  else
534  return union_dbscan_general(geoms, num_geoms, uf, eps, min_points, in_a_cluster_ret);
535 }
536 
540 int
541 cluster_within_distance(LWGEOM** geoms, uint32_t num_geoms, double tolerance, LWGEOM*** clusterGeoms, uint32_t* num_clusters)
542 {
543  int cluster_success;
544  UNIONFIND* uf = UF_create(num_geoms);
545 
546  if (union_dbscan(geoms, num_geoms, uf, tolerance, 1, NULL) == LW_FAILURE)
547  {
548  UF_destroy(uf);
549  return LW_FAILURE;
550  }
551 
552  cluster_success = combine_geometries(uf, (void**) geoms, num_geoms, (void***) clusterGeoms, num_clusters, 1);
553  UF_destroy(uf);
554  return cluster_success;
555 }
556 
560 static int
561 combine_geometries(UNIONFIND* uf, void** geoms, uint32_t num_geoms, void*** clusterGeoms, uint32_t* num_clusters, char is_lwgeom)
562 {
563  size_t i, j, k;
564 
565  /* Combine components of each cluster into their own GeometryCollection */
566  *num_clusters = uf->num_clusters;
567  *clusterGeoms = lwalloc(*num_clusters * sizeof(void*));
568 
569  void** geoms_in_cluster = lwalloc(num_geoms * sizeof(void*));
570  uint32_t* ordered_components = UF_ordered_by_cluster(uf);
571  for (i = 0, j = 0, k = 0; i < num_geoms; i++)
572  {
573  geoms_in_cluster[j++] = geoms[ordered_components[i]];
574 
575  /* Is this the last geometry in the component? */
576  if ((i == num_geoms - 1) ||
577  (UF_find(uf, ordered_components[i]) != UF_find(uf, ordered_components[i+1])))
578  {
579  if (k >= uf->num_clusters) {
580  /* Should not get here - it means that we have more clusters than uf->num_clusters thinks we should. */
581  return LW_FAILURE;
582  }
583 
584  if (is_lwgeom)
585  {
586  LWGEOM** components = lwalloc(j * sizeof(LWGEOM*));
587  memcpy(components, geoms_in_cluster, j * sizeof(LWGEOM*));
588  (*clusterGeoms)[k++] = lwcollection_construct(COLLECTIONTYPE, components[0]->srid, NULL, j, (LWGEOM**) components);
589  }
590  else
591  {
592  int srid = GEOSGetSRID(((GEOSGeometry**) geoms_in_cluster)[0]);
593  GEOSGeometry* combined = GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION, (GEOSGeometry**) geoms_in_cluster, j);
594  GEOSSetSRID(combined, srid);
595  (*clusterGeoms)[k++] = combined;
596  }
597  j = 0;
598  }
599  }
600 
601  lwfree(geoms_in_cluster);
602  lwfree(ordered_components);
603 
604  return LW_SUCCESS;
605 }
GEOSGeometry * make_geos_point(double x, double y)
GEOSGeometry * make_geos_segment(double x1, double y1, double x2, double y2)
#define LW_FALSE
Definition: liblwgeom.h:109
#define COLLECTIONTYPE
Definition: liblwgeom.h:123
#define LW_FAILURE
Definition: liblwgeom.h:111
#define LW_SUCCESS
Definition: liblwgeom.h:112
#define POINTTYPE
LWTYPE numbers, used internally by PostGIS.
Definition: liblwgeom.h:117
void * lwrealloc(void *mem, size_t size)
Definition: lwutil.c:235
void lwfree(void *mem)
Definition: lwutil.c:242
double lwgeom_mindistance2d_tolerance(const LWGEOM *lw1, const LWGEOM *lw2, double tolerance)
Function handling min distance calculations and dwithin calculations.
Definition: measures.c:210
const GBOX * lwgeom_get_bbox(const LWGEOM *lwgeom)
Get a non-empty geometry bounding box, computing and caching it if not already there.
Definition: lwgeom.c:743
void * lwalloc(size_t size)
Definition: lwutil.c:227
LWCOLLECTION * lwcollection_construct(uint8_t type, int32_t srid, GBOX *bbox, uint32_t ngeoms, LWGEOM **geoms)
Definition: lwcollection.c:42
#define LW_TRUE
Return types for functions with status returns.
Definition: liblwgeom.h:108
This library is the generic geometry handling section of PostGIS.
#define LW_ON_INTERRUPT(x)
static int dbscan_update_context(GEOSSTRtree *tree, struct QueryContext *cxt, LWGEOM **geoms, uint32_t p, double eps)
static GEOSGeometry * geos_envelope_surrogate(const LWGEOM *g)
static int union_dbscan_general(LWGEOM **geoms, uint32_t num_geoms, UNIONFIND *uf, double eps, uint32_t min_points, char **in_a_cluster_ret)
int union_dbscan(LWGEOM **geoms, uint32_t num_geoms, UNIONFIND *uf, double eps, uint32_t min_points, char **in_a_cluster_ret)
static int union_dbscan_minpoints_1(LWGEOM **geoms, uint32_t num_geoms, UNIONFIND *uf, double eps, char **in_a_cluster_ret)
static void query_accumulate(void *item, void *userdata)
static struct STRTree make_strtree(void **geoms, uint32_t num_geoms, char is_lwgeom)
Make a GEOSSTRtree that stores a pointer to a variable containing the array index of the input geoms.
static void destroy_strtree(struct STRTree *tree)
Clean up STRTree after use.
static const int STRTREE_NODE_CAPACITY
static void union_if_available(UNIONFIND *uf, uint32_t p, uint32_t q, char *is_in_core, char *in_a_cluster)
int cluster_within_distance(LWGEOM **geoms, uint32_t num_geoms, double tolerance, LWGEOM ***clusterGeoms, uint32_t *num_clusters)
Takes an array of LWGEOM* and constructs an array of LWGEOM*, where each element in the constructed a...
int cluster_intersecting(GEOSGeometry **geoms, uint32_t num_geoms, GEOSGeometry ***clusterGeoms, uint32_t *num_clusters)
Takes an array of GEOSGeometry* and constructs an array of GEOSGeometry*, where each element in the c...
static int union_intersecting_pairs(GEOSGeometry **geoms, uint32_t num_geoms, UNIONFIND *uf)
static int combine_geometries(UNIONFIND *uf, void **geoms, uint32_t num_geoms, void ***clustersGeoms, uint32_t *num_clusters, char is_lwgeom)
Uses a UNIONFIND to identify the set with which each input geometry is associated,...
static const POINT2D * getPoint2d_cp(const POINTARRAY *pa, uint32_t n)
Returns a POINT2D pointer into the POINTARRAY serialized_ptlist, suitable for reading from.
Definition: lwinline.h:101
static uint32_t lwgeom_get_type(const LWGEOM *geom)
Return LWTYPE number.
Definition: lwinline.h:145
static int lwgeom_is_empty(const LWGEOM *geom)
Return true or false depending on whether a geometry is an "empty" geometry (no vertices members)
Definition: lwinline.h:203
static LWPOINT * lwgeom_as_lwpoint(const LWGEOM *lwgeom)
Definition: lwinline.h:131
void UF_destroy(UNIONFIND *uf)
Definition: lwunionfind.c:54
uint32_t UF_find(UNIONFIND *uf, uint32_t i)
Definition: lwunionfind.c:62
UNIONFIND * UF_create(uint32_t N)
Definition: lwunionfind.c:35
uint32_t * UF_ordered_by_cluster(UNIONFIND *uf)
Definition: lwunionfind.c:113
void UF_union(UNIONFIND *uf, uint32_t i, uint32_t j)
Definition: lwunionfind.c:85
type
Definition: ovdump.py:42
double ymax
Definition: liblwgeom.h:372
double xmax
Definition: liblwgeom.h:370
double ymin
Definition: liblwgeom.h:371
double xmin
Definition: liblwgeom.h:369
double y
Definition: liblwgeom.h:405
double x
Definition: liblwgeom.h:405
uint32_t num_items_found
uint32_t items_found_size
GEOSGeometry ** envelopes
uint32_t * geom_ids
uint32_t num_geoms
GEOSSTRtree * tree
uint32_t num_clusters
Definition: lwunionfind.h:35