PostGIS  3.4.0dev-r@@SVN_REVISION@@
lwgeom_window.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 2016 Paul Ramsey <pramsey@cleverelephant.ca>
22  * Copyright 2016 Daniel Baston <dbaston@gmail.com>
23  *
24  **********************************************************************/
25 
26 #include "../postgis_config.h"
27 
28 /* PostgreSQL */
29 #include "postgres.h"
30 #include "funcapi.h"
31 #include "windowapi.h"
32 
33 /* PostGIS */
34 #include "liblwgeom.h"
35 #include "lwunionfind.h" /* TODO move into liblwgeom.h ? */
36 #include "lwgeom_geos.h"
37 #include "lwgeom_log.h"
38 #include "lwgeom_pg.h"
39 
40 
41 typedef struct {
42  bool isdone;
43  bool isnull;
44  int result[1];
45  /* variable length */
47 
48 typedef struct
49 {
50  uint32_t cluster_id;
51  char is_null; /* NULL may result from a NULL geometry input, or it may be used by
52  algorithms such as DBSCAN that do not assign all inputs to a
53  cluster. */
55 
56 typedef struct
57 {
58  char is_error;
59  cluster_entry clusters[1];
61 
62 static cluster_context*
63 fetch_cluster_context(WindowObject win_obj, uint32_t ngeoms)
64 {
65  size_t context_sz = sizeof(cluster_context) + (ngeoms * sizeof(cluster_entry));
66  cluster_context* context = WinGetPartitionLocalMemory(win_obj, context_sz);
67  return context;
68 }
69 
70 static LWGEOM*
71 read_lwgeom_from_partition(WindowObject win_obj, uint32_t i, bool* is_null)
72 {
73  GSERIALIZED* g;
74  Datum arg = WinGetFuncArgInPartition(win_obj, 0, i, WINDOW_SEEK_HEAD, false, is_null, NULL);
75 
76  if (*is_null) {
77  /* So that the indexes in our clustering input array can match our partition positions,
78  * toss an empty point into the clustering inputs, as a pass-through.
79  * NOTE: this will cause gaps in the output cluster id sequence.
80  * */
82  }
83 
84  g = (GSERIALIZED*) PG_DETOAST_DATUM_COPY(arg);
85  return lwgeom_from_gserialized(g);
86 }
87 
88 static GEOSGeometry*
89 read_geos_from_partition(WindowObject win_obj, uint32_t i, bool* is_null)
90 {
91  GSERIALIZED* g;
92  LWGEOM* lwg;
93  GEOSGeometry* gg;
94  Datum arg = WinGetFuncArgInPartition(win_obj, 0, i, WINDOW_SEEK_HEAD, false, is_null, NULL);
95 
96  if (*is_null) {
97  /* So that the indexes in our clustering input array can match our partition positions,
98  * toss an empty point into the clustering inputs, as a pass-through.
99  * NOTE: this will cause gaps in the output cluster id sequence.
100  * */
102  gg = LWGEOM2GEOS(lwg, LW_FALSE);
103  lwgeom_free(lwg);
104  return gg;
105  }
106 
107  g = (GSERIALIZED*) PG_DETOAST_DATUM_COPY(arg);
108  lwg = lwgeom_from_gserialized(g);
109  gg = LWGEOM2GEOS(lwg, LW_TRUE);
110  lwgeom_free(lwg);
111  if (!gg) {
112  *is_null = LW_TRUE;
113  }
114  return gg;
115 }
116 
117 extern Datum ST_ClusterDBSCAN(PG_FUNCTION_ARGS);
119 Datum ST_ClusterDBSCAN(PG_FUNCTION_ARGS)
120 {
121  WindowObject win_obj = PG_WINDOW_OBJECT();
122  uint32_t row = WinGetCurrentPosition(win_obj);
123  uint32_t ngeoms = WinGetPartitionRowCount(win_obj);
124  cluster_context* context = fetch_cluster_context(win_obj, ngeoms);
125 
126  if (row == 0) /* beginning of the partition; do all of the work now */
127  {
128  uint32_t i;
129  uint32_t* result_ids;
130  LWGEOM** geoms;
131  char* is_in_cluster = NULL;
132  UNIONFIND* uf;
133  bool tolerance_is_null;
134  bool minpoints_is_null;
135  Datum tolerance_datum = WinGetFuncArgCurrent(win_obj, 1, &tolerance_is_null);
136  Datum minpoints_datum = WinGetFuncArgCurrent(win_obj, 2, &minpoints_is_null);
137  double tolerance = DatumGetFloat8(tolerance_datum);
138  int minpoints = DatumGetInt32(minpoints_datum);
139 
140  context->is_error = LW_TRUE; /* until proven otherwise */
141 
142  /* Validate input parameters */
143  if (tolerance_is_null || tolerance < 0)
144  {
145  lwpgerror("Tolerance must be a positive number", tolerance);
146  PG_RETURN_NULL();
147  }
148  if (minpoints_is_null || minpoints < 0)
149  {
150  lwpgerror("Minpoints must be a positive number", minpoints);
151  }
152 
153  initGEOS(lwnotice, lwgeom_geos_error);
154  geoms = lwalloc(ngeoms * sizeof(LWGEOM*));
155  uf = UF_create(ngeoms);
156  for (i = 0; i < ngeoms; i++)
157  {
158  bool geom_is_null;
159  geoms[i] = read_lwgeom_from_partition(win_obj, i, &geom_is_null);
160  context->clusters[i].is_null = geom_is_null;
161 
162  if (!geoms[i]) {
163  /* TODO release memory ? */
164  lwpgerror("Error reading geometry.");
165  PG_RETURN_NULL();
166  }
167  }
168 
169  if (union_dbscan(geoms, ngeoms, uf, tolerance, minpoints, minpoints > 1 ? &is_in_cluster : NULL) == LW_SUCCESS)
170  context->is_error = LW_FALSE;
171 
172  for (i = 0; i < ngeoms; i++)
173  {
174  lwgeom_free(geoms[i]);
175  }
176  lwfree(geoms);
177 
178  if (context->is_error)
179  {
180  UF_destroy(uf);
181  if (is_in_cluster)
182  lwfree(is_in_cluster);
183  lwpgerror("Error during clustering");
184  PG_RETURN_NULL();
185  }
186 
187  result_ids = UF_get_collapsed_cluster_ids(uf, is_in_cluster);
188  for (i = 0; i < ngeoms; i++)
189  {
190  if (minpoints > 1 && !is_in_cluster[i])
191  {
192  context->clusters[i].is_null = LW_TRUE;
193  }
194  else
195  {
196  context->clusters[i].cluster_id = result_ids[i];
197  }
198  }
199 
200  lwfree(result_ids);
201  UF_destroy(uf);
202  }
203 
204  if (context->clusters[row].is_null)
205  PG_RETURN_NULL();
206 
207  PG_RETURN_INT32(context->clusters[row].cluster_id);
208 }
209 
210 extern Datum ST_ClusterWithinWin(PG_FUNCTION_ARGS);
212 Datum ST_ClusterWithinWin(PG_FUNCTION_ARGS)
213 {
214  WindowObject win_obj = PG_WINDOW_OBJECT();
215  uint32_t row = WinGetCurrentPosition(win_obj);
216  uint32_t ngeoms = WinGetPartitionRowCount(win_obj);
217  cluster_context* context = fetch_cluster_context(win_obj, ngeoms);
218 
219  if (row == 0) /* beginning of the partition; do all of the work now */
220  {
221  uint32_t i;
222  uint32_t* result_ids;
223  LWGEOM** geoms;
224  UNIONFIND* uf;
225  bool tolerance_is_null;
226  double tolerance = DatumGetFloat8(WinGetFuncArgCurrent(win_obj, 1, &tolerance_is_null));
227 
228  /* Validate input parameters */
229  if (tolerance_is_null || tolerance < 0)
230  {
231  lwpgerror("Tolerance must be a positive number", tolerance);
232  PG_RETURN_NULL();
233  }
234 
235  context->is_error = LW_TRUE; /* until proven otherwise */
236 
237  geoms = lwalloc(ngeoms * sizeof(LWGEOM*));
238  uf = UF_create(ngeoms);
239  for (i = 0; i < ngeoms; i++)
240  {
241  bool geom_is_null;
242  geoms[i] = read_lwgeom_from_partition(win_obj, i, &geom_is_null);
243  context->clusters[i].is_null = geom_is_null;
244 
245  if (!geoms[i])
246  {
247  lwpgerror("Error reading geometry.");
248  PG_RETURN_NULL();
249  }
250  }
251 
252  initGEOS(lwpgnotice, lwgeom_geos_error);
253 
254  if (union_dbscan(geoms, ngeoms, uf, tolerance, 1, NULL) == LW_SUCCESS)
255  context->is_error = LW_FALSE;
256 
257  for (i = 0; i < ngeoms; i++)
258  {
259  lwgeom_free(geoms[i]);
260  }
261  lwfree(geoms);
262 
263  if (context->is_error)
264  {
265  UF_destroy(uf);
266  lwpgerror("Error during clustering");
267  PG_RETURN_NULL();
268  }
269 
270  result_ids = UF_get_collapsed_cluster_ids(uf, NULL);
271  for (i = 0; i < ngeoms; i++)
272  {
273  context->clusters[i].cluster_id = result_ids[i];
274  }
275 
276  lwfree(result_ids);
277  UF_destroy(uf);
278  }
279 
280  if (context->clusters[row].is_null)
281  PG_RETURN_NULL();
282 
283  PG_RETURN_INT32(context->clusters[row].cluster_id);
284 }
285 
286 extern Datum ST_ClusterIntersectingWin(PG_FUNCTION_ARGS);
288 Datum ST_ClusterIntersectingWin(PG_FUNCTION_ARGS)
289 {
290  WindowObject win_obj = PG_WINDOW_OBJECT();
291  uint32_t row = WinGetCurrentPosition(win_obj);
292  uint32_t ngeoms = WinGetPartitionRowCount(win_obj);
293  cluster_context* context = fetch_cluster_context(win_obj, ngeoms);
294 
295  if (row == 0) /* beginning of the partition; do all of the work now */
296  {
297  uint32_t i;
298  uint32_t* result_ids;
299  GEOSGeometry** geoms = lwalloc(ngeoms * sizeof(GEOSGeometry*));
300  UNIONFIND* uf = UF_create(ngeoms);
301 
302  context->is_error = LW_TRUE; /* until proven otherwise */
303 
304  initGEOS(lwpgnotice, lwgeom_geos_error);
305 
306  for (i = 0; i < ngeoms; i++)
307  {
308  bool geom_is_null;
309  geoms[i] = read_geos_from_partition(win_obj, i, &geom_is_null);
310  context->clusters[i].is_null = geom_is_null;
311 
312  if (!geoms[i])
313  {
314  lwpgerror("Error reading geometry.");
315  PG_RETURN_NULL();
316  }
317  }
318 
319  if (union_intersecting_pairs(geoms, ngeoms, uf) == LW_SUCCESS)
320  context->is_error = LW_FALSE;
321 
322  for (i = 0; i < ngeoms; i++)
323  {
324  GEOSGeom_destroy(geoms[i]);
325  }
326  lwfree(geoms);
327 
328  if (context->is_error)
329  {
330  UF_destroy(uf);
331  lwpgerror("Error during clustering");
332  PG_RETURN_NULL();
333  }
334 
335  result_ids = UF_get_collapsed_cluster_ids(uf, NULL);
336  for (i = 0; i < ngeoms; i++)
337  {
338  context->clusters[i].cluster_id = result_ids[i];
339  }
340 
341  lwfree(result_ids);
342  UF_destroy(uf);
343  }
344 
345  if (context->clusters[row].is_null)
346  PG_RETURN_NULL();
347 
348  PG_RETURN_INT32(context->clusters[row].cluster_id);
349 }
350 
351 
352 extern Datum ST_ClusterKMeans(PG_FUNCTION_ARGS);
354 Datum ST_ClusterKMeans(PG_FUNCTION_ARGS)
355 {
356  WindowObject winobj = PG_WINDOW_OBJECT();
357  kmeans_context *context;
358  int64 curpos, rowcount;
359 
360  rowcount = WinGetPartitionRowCount(winobj);
361  context = (kmeans_context *)
362  WinGetPartitionLocalMemory(winobj,
363  sizeof(kmeans_context) + sizeof(int) * rowcount);
364 
365  if (!context->isdone)
366  {
367  int i, k, N;
368  bool isnull, isout;
369  double max_radius = 0.0;
370  LWGEOM **geoms;
371  int *r;
372  Datum argdatum;
373 
374  /* What is K? If it's NULL or invalid, we can't procede */
375  argdatum = WinGetFuncArgCurrent(winobj, 1, &isnull);
376  k = DatumGetInt32(argdatum);
377  if (isnull || k <= 0)
378  {
379  context->isdone = true;
380  context->isnull = true;
381  PG_RETURN_NULL();
382  }
383 
384  /* We also need a non-zero N */
385  N = (int) WinGetPartitionRowCount(winobj);
386  if (N <= 0)
387  {
388  context->isdone = true;
389  context->isnull = true;
390  PG_RETURN_NULL();
391  }
392 
393  /* Maximum cluster radius. 0 if not set*/
394  argdatum = WinGetFuncArgCurrent(winobj, 2, &isnull);
395  if (!isnull)
396  {
397  max_radius = DatumGetFloat8(argdatum);
398  if (max_radius < 0)
399  max_radius = 0.0;
400  }
401 
402  /* Error out if N < K */
403  if (N<k)
404  lwpgerror("K (%d) must be smaller than the number of rows in the group (%d)", k, N);
405 
406  /* Read all the geometries from the partition window into a list */
407  geoms = palloc(sizeof(LWGEOM*) * N);
408  for (i = 0; i < N; i++)
409  {
410  GSERIALIZED *g;
411  Datum arg = WinGetFuncArgInPartition(winobj, 0, i,
412  WINDOW_SEEK_HEAD, false, &isnull, &isout);
413 
414  /* Null geometries are entered as NULL pointers */
415  if (isnull)
416  {
417  geoms[i] = NULL;
418  continue;
419  }
420 
421  g = (GSERIALIZED*)PG_DETOAST_DATUM_COPY(arg);
422  geoms[i] = lwgeom_from_gserialized(g);
423  }
424 
425  /* Calculate k-means on the list! */
426  r = lwgeom_cluster_kmeans((const LWGEOM **)geoms, N, k, max_radius);
427 
428  /* Clean up */
429  for (i = 0; i < N; i++)
430  if (geoms[i])
431  lwgeom_free(geoms[i]);
432 
433  pfree(geoms);
434 
435  if (!r)
436  {
437  context->isdone = true;
438  context->isnull = true;
439  PG_RETURN_NULL();
440  }
441 
442  /* Safe the result */
443  memcpy(context->result, r, sizeof(int) * N);
444  lwfree(r);
445  context->isdone = true;
446  }
447 
448  if (context->isnull)
449  PG_RETURN_NULL();
450 
451  curpos = WinGetCurrentPosition(winobj);
452  PG_RETURN_INT32(context->result[curpos]);
453 }
454 
455 
456 /********************************************************************************
457  * GEOS Coverage Functions
458  *
459  * The GEOS "coverage" support is a set of functions for working with
460  * "implicit coverages". That is, collections of polygonal geometry
461  * that have perfectly shared edges. Since perfectly matched edges are
462  * fast to detect, "building a coverage" on these inputs is fast, so
463  * operations like edge simplification and so on can include the "build"
464  * step with relatively low cost.
465  *
466  */
467 
468 #if POSTGIS_GEOS_VERSION >= 30800
469 /*
470  * For CoverageUnion and CoverageSimplify, clean up
471  * the start of a collection if things fail in mid-stream.
472  */
473 static void
474 coverage_destroy_geoms(GEOSGeometry **geoms, uint32 ngeoms)
475 {
476  if (!geoms) return;
477  if (!ngeoms) return;
478  for (uint32 i = 0; i < ngeoms; i++)
479  {
480  if(geoms[i])
481  GEOSGeom_destroy(geoms[i]);
482  }
483 }
484 #endif
485 
486 #if POSTGIS_GEOS_VERSION >= 31200
487 
488 /*
489  * For CoverageIsValid and CoverageSimplify, maintain context
490  * of unioned output and the position of the resultant in that
491  * output relative to the index of the input geometries.
492  */
493 typedef struct {
494  bool isdone;
495  bool isnull;
497  int64 idx[0];
498  /* variable length */
500 
501 /*
502  * For CoverageIsValid and CoverageSimplify, read the context
503  * out of the WindowObject.
504  */
505 static coverage_context *
506 coverage_context_fetch(WindowObject winobj, int64 rowcount)
507 {
508  size_t ctx_size = sizeof(coverage_context) + rowcount * sizeof(int64);
509  coverage_context *ctx = (coverage_context*) WinGetPartitionLocalMemory(
510  winobj, ctx_size);
511  return ctx;
512 }
513 
514 /*
515  * For CoverageIsValid and CoverageSimplify, read all the
516  * geometries in this partition and form a GEOS geometry
517  * collection out of them.
518  */
519 static GEOSGeometry*
521  WindowObject winobj,
522  coverage_context* context)
523 {
524  int64 rowcount = WinGetPartitionRowCount(winobj);
525  GEOSGeometry* geos;
526  GEOSGeometry** geoms;
527  uint32 i, ngeoms = 0, gtype;
528 
529  /* Read in all the geometries in this partition */
530  geoms = palloc(rowcount * sizeof(GEOSGeometry*));
531  for (i = 0; i < rowcount; i++)
532  {
533  GSERIALIZED* gser;
534  bool isnull, isout;
535  bool isempty, ispolygonal;
536  Datum d;
537 
538  /* Read geometry in first argument */
539  d = WinGetFuncArgInPartition(winobj, 0, i,
540  WINDOW_SEEK_HEAD, false, &isnull, &isout);
541 
542  /*
543  * The input to the GEOS function will be smaller than
544  * the input to this window, since we will not feed
545  * GEOS nulls or empties. So we need to maintain a
546  * map (context->idx) from the window position of the
547  * input to the GEOS position, so we can put the
548  * right result in the output stream.
549  */
550 
551  /* Skip NULL inputs and move on */
552  if (isnull)
553  {
554  context->idx[i] = -1;
555  continue;
556  }
557 
558  gser = (GSERIALIZED*)PG_DETOAST_DATUM(d);
559  gtype = gserialized_get_type(gser);
560  isempty = gserialized_is_empty(gser);
561  ispolygonal = (gtype == MULTIPOLYGONTYPE) || (gtype == POLYGONTYPE);
562 
563  /* Skip empty or non-polygonal inputs */
564  if (isempty || !ispolygonal)
565  {
566  context->idx[i] = -1;
567  continue;
568  }
569 
570  /* Skip failed inputs */
571  geos = POSTGIS2GEOS(gser);
572  if (!geos)
573  {
574  context->idx[i] = -1;
575  continue;
576  }
577 
578  context->idx[i] = ngeoms;
579  geoms[ngeoms] = geos;
580  ngeoms = ngeoms + 1;
581  }
582 
583  /*
584  * Create the GEOS input collection! The new
585  * collection takes ownership of the input GEOSGeometry
586  * objects, leaving just the ngeoms array, which
587  * will be cleaned up on function exit.
588  */
589  geos = GEOSGeom_createCollection(
590  GEOS_GEOMETRYCOLLECTION,
591  geoms, ngeoms);
592 
593  /*
594  * If the creation failed, the objects will still be
595  * hanging around, so clean them up first. Should
596  * never happen, really.
597  */
598  if (!geos)
599  {
600  coverage_destroy_geoms(geoms, ngeoms);
601  return NULL;
602  }
603 
604  pfree(geoms);
605  return geos;
606 }
607 
608 
609 /*
610  * The coverage_window_calculation function is just
611  * the shared machinery of the two window functions
612  * CoverageIsValid and CoverageSimplify which are almost
613  * the same except the GEOS function they call. That
614  * operating mode is controlled with this enumeration.
615  */
616 enum {
618  COVERAGE_ISVALID = 1
619 };
620 
621 /*
622  * This calculation is shared by both coverage operations
623  * since they have the same pattern of "consume collection,
624  * return collection", and are both window functions.
625  */
626 static Datum
627 coverage_window_calculation(PG_FUNCTION_ARGS, int mode)
628 {
629  WindowObject winobj = PG_WINDOW_OBJECT();
630  int64 curpos = WinGetCurrentPosition(winobj);
631  int64 rowcount = WinGetPartitionRowCount(winobj);
632  coverage_context *context = coverage_context_fetch(winobj, rowcount);
634  MemoryContext uppercontext = fcinfo->flinfo->fn_mcxt;
635  MemoryContext oldcontext;
636  const LWGEOM* subgeom;
637 
638  if (!context->isdone)
639  {
640  bool isnull;
641  Datum d;
642  double tolerance = 0.0;
643  bool simplifyBoundary = true;
644  GEOSGeometry *output = NULL;
645  GEOSGeometry *input = NULL;
646 
647  if (!fcinfo->flinfo)
648  elog(ERROR, "%s: Could not find upper context", __func__);
649 
650  if (rowcount == 0)
651  {
652  context->isdone = true;
653  context->isnull = true;
654  PG_RETURN_NULL();
655  }
656 
657  /* Get the tolerance argument from second postition */
658  d = WinGetFuncArgCurrent(winobj, 1, &isnull);
659  if (!isnull)
660  tolerance = DatumGetFloat8(d);
661 
662  /* The third position "preserve boundary" argument */
663  /* is only for the simplify mode */
664  if (mode == COVERAGE_SIMPLIFY)
665  {
666  d = WinGetFuncArgCurrent(winobj, 2, &isnull);
667  if (!isnull)
668  simplifyBoundary = DatumGetBool(d);
669  }
670 
671  initGEOS(lwnotice, lwgeom_geos_error);
672 
673  input = coverage_read_partition_into_collection(winobj, context);
674  if (!input)
675  HANDLE_GEOS_ERROR("Failed to create collection");
676 
677  /* Run the correct GEOS function for the calling mode */
678  if (mode == COVERAGE_SIMPLIFY)
679  {
680  /* GEOSCoverageSimplifyVW is "preserveBoundary" so we invert simplifyBoundary */
681  output = GEOSCoverageSimplifyVW(input, tolerance, !simplifyBoundary);
682  }
683  else if (mode == COVERAGE_ISVALID)
684  {
685  GEOSCoverageIsValid(input, tolerance, &output);
686  }
687  else
688  {
689  elog(ERROR, "Unknown mode, never get here");
690  }
691 
692  GEOSGeom_destroy(input);
693 
694  if (!output)
695  {
696  HANDLE_GEOS_ERROR("Failed to process collection");
697  }
698 
699  oldcontext = MemoryContextSwitchTo(uppercontext);
700  context->geom = (LWCOLLECTION *) GEOS2LWGEOM(output, GEOSHasZ(output));
701  MemoryContextSwitchTo(oldcontext);
702  GEOSGeom_destroy(output);
703 
704  context->isdone = true;
705  }
706 
707  /* Bomb out of any errors */
708  if (context->isnull)
709  PG_RETURN_NULL();
710 
711  /* Propogate the null entries */
712  if (context->idx[curpos] < 0)
713  PG_RETURN_NULL();
714 
715  /*
716  * Geometry serialization is not const-safe! (we
717  * calculate bounding boxes on demand) so we need
718  * to make sure we have a persistent context when
719  * we call the serialization, lest we create dangling
720  * pointers in the object. It's possible we could
721  * ignore them, skip the manual lwcollection_free
722  * and let the aggcontext deletion take
723  * care of the memory.
724  */
725  oldcontext = MemoryContextSwitchTo(uppercontext);
726  subgeom = lwcollection_getsubgeom(context->geom, context->idx[curpos]);
727  if (mode == COVERAGE_ISVALID && lwgeom_is_empty(subgeom))
728  {
729  result = NULL;
730  }
731  else
732  {
733  result = geometry_serialize((LWGEOM*)subgeom);
734  }
735  MemoryContextSwitchTo(oldcontext);
736 
737  /* When at the end of the partition, release the */
738  /* simplified collection we have been reading. */
739  if (curpos == rowcount - 1)
740  lwcollection_free(context->geom);
741 
742  if (!result) PG_RETURN_NULL();
743 
744  PG_RETURN_POINTER(result);
745 
746 }
747 #endif /* POSTGIS_GEOS_VERSION >= 31200 */
748 
749 
750 extern Datum ST_CoverageSimplify(PG_FUNCTION_ARGS);
752 Datum ST_CoverageSimplify(PG_FUNCTION_ARGS)
753 {
754 #if POSTGIS_GEOS_VERSION < 31200
755 
756  lwpgerror("The GEOS version this PostGIS binary "
757  "was compiled against (%d) doesn't support "
758  "'GEOSCoverageSimplifyVW' function (3.12.0+ required)",
760  PG_RETURN_NULL();
761 
762 #else /* POSTGIS_GEOS_VERSION >= 31200 */
763 
765 
766 #endif
767 }
768 
769 
770 extern Datum ST_CoverageInvalidEdges(PG_FUNCTION_ARGS);
772 Datum ST_CoverageInvalidEdges(PG_FUNCTION_ARGS)
773 {
774 #if POSTGIS_GEOS_VERSION < 31200
775 
776  lwpgerror("The GEOS version this PostGIS binary "
777  "was compiled against (%d) doesn't support "
778  "'GEOSCoverageIsValid' function (3.12.0+ required)",
780  PG_RETURN_NULL();
781 
782 #else /* POSTGIS_GEOS_VERSION >= 31200 */
783 
785 
786 #endif
787 }
788 
789 /**********************************************************************
790  * ST_CoverageUnion(geometry[])
791  *
792  */
793 
794 Datum ST_CoverageUnion(PG_FUNCTION_ARGS);
796 Datum ST_CoverageUnion(PG_FUNCTION_ARGS)
797 {
798 #if POSTGIS_GEOS_VERSION < 30800
799  lwpgerror("The GEOS version this PostGIS binary "
800  "was compiled against (%d) doesn't support "
801  "'GEOSCoverageUnion' function (3.8.0+ required)",
803  PG_RETURN_NULL();
804 
805 #else /* POSTGIS_GEOS_VERSION >= 30800 */
806 
807  GSERIALIZED *result = NULL;
808 
809  Datum value;
810  bool isnull;
811 
812  GEOSGeometry **geoms = NULL;
813  GEOSGeometry *geos = NULL;
814  GEOSGeometry *geos_result = NULL;
815  uint32 ngeoms = 0;
816 
817  ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
818  uint32 nelems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
819  ArrayIterator iterator = array_create_iterator(array, 0, NULL);
820 
821  /* Return null on 0-elements input array */
822  if (nelems == 0)
823  PG_RETURN_NULL();
824 
825  /* Convert all geometries into GEOSGeometry array */
826  geoms = palloc(sizeof(GEOSGeometry *) * nelems);
827 
828  initGEOS(lwpgnotice, lwgeom_geos_error);
829 
830  while (array_iterate(iterator, &value, &isnull))
831  {
832  GSERIALIZED *gser;
833  /* Omit nulls */
834  if (isnull) continue;
835 
836  /* Omit empty */
837  gser = (GSERIALIZED *)DatumGetPointer(value);
838  if (gserialized_is_empty(gser)) continue;
839 
840  /* Omit unconvertable */
841  geos = POSTGIS2GEOS(gser);
842  if (!geos) continue;
843 
844  geoms[ngeoms++] = geos;
845  }
846  array_free_iterator(iterator);
847 
848  if (ngeoms == 0)
849  PG_RETURN_NULL();
850 
851  geos = GEOSGeom_createCollection(
852  GEOS_GEOMETRYCOLLECTION,
853  geoms, ngeoms);
854 
855  if (!geos)
856  {
857  coverage_destroy_geoms(geoms, ngeoms);
858  HANDLE_GEOS_ERROR("Geometry could not be converted");
859  }
860 
861  geos_result = GEOSCoverageUnion(geos);
862  GEOSGeom_destroy(geos);
863  if (!geos_result)
864  HANDLE_GEOS_ERROR("Error computing coverage union");
865 
866  result = GEOS2POSTGIS(geos_result, LW_FALSE);
867  GEOSGeom_destroy(geos_result);
868 
869  PG_RETURN_POINTER(result);
870 #endif
871 }
872 
873 
char * r
Definition: cu_in_wkt.c:24
char result[OUT_DOUBLE_BUFFER_SIZE]
Definition: cu_print.c:262
LWGEOM * lwgeom_from_gserialized(const GSERIALIZED *g)
Allocate a new LWGEOM from a GSERIALIZED.
Definition: gserialized.c:239
int gserialized_is_empty(const GSERIALIZED *g)
Check if a GSERIALIZED is empty without deserializing first.
Definition: gserialized.c:152
uint32_t gserialized_get_type(const GSERIALIZED *g)
Extract the geometry type from the serialized form (it hides in the anonymous data area,...
Definition: gserialized.c:89
GEOSGeometry * LWGEOM2GEOS(const LWGEOM *lwgeom, uint8_t autofix)
LWGEOM * GEOS2LWGEOM(const GEOSGeometry *geom, uint8_t want3d)
void lwgeom_geos_error(const char *fmt,...)
int union_intersecting_pairs(GEOSGeometry **geoms, uint32_t num_geoms, UNIONFIND *uf)
int union_dbscan(LWGEOM **geoms, uint32_t num_geoms, UNIONFIND *uf, double eps, uint32_t min_points, char **is_in_cluster_ret)
LWPOINT * lwpoint_construct_empty(int32_t srid, char hasz, char hasm)
Definition: lwpoint.c:151
#define LW_FALSE
Definition: liblwgeom.h:94
void lwgeom_free(LWGEOM *geom)
Definition: lwgeom.c:1155
LWGEOM * lwcollection_getsubgeom(LWCOLLECTION *col, int gnum)
Definition: lwcollection.c:114
#define LW_SUCCESS
Definition: liblwgeom.h:97
#define MULTIPOLYGONTYPE
Definition: liblwgeom.h:107
int * lwgeom_cluster_kmeans(const LWGEOM **geoms, uint32_t n, uint32_t k, double max_radius)
Take a list of LWGEOMs and a number of clusters and return an integer array indicating which cluster ...
Definition: lwkmeans.c:321
void lwfree(void *mem)
Definition: lwutil.c:242
LWGEOM * lwpoint_as_lwgeom(const LWPOINT *obj)
Definition: lwgeom.c:344
#define POLYGONTYPE
Definition: liblwgeom.h:104
void lwcollection_free(LWCOLLECTION *col)
Definition: lwcollection.c:357
void * lwalloc(size_t size)
Definition: lwutil.c:227
#define LW_TRUE
Return types for functions with status returns.
Definition: liblwgeom.h:93
This library is the generic geometry handling section of PostGIS.
void lwnotice(const char *fmt,...)
Write a notice out to the notice handler.
Definition: lwutil.c:177
@ COVERAGE_ISVALID
@ COVERAGE_SIMPLIFY
Datum ST_CoverageInvalidEdges(PG_FUNCTION_ARGS)
Datum ST_ClusterWithinWin(PG_FUNCTION_ARGS)
PG_FUNCTION_INFO_V1(ST_ClusterDBSCAN)
Datum ST_ClusterIntersectingWin(PG_FUNCTION_ARGS)
Datum ST_CoverageUnion(PG_FUNCTION_ARGS)
static GEOSGeometry * read_geos_from_partition(WindowObject win_obj, uint32_t i, bool *is_null)
Definition: lwgeom_window.c:89
static coverage_context * coverage_context_fetch(WindowObject winobj, int64 rowcount)
static GEOSGeometry * coverage_read_partition_into_collection(WindowObject winobj, coverage_context *context)
Datum ST_CoverageSimplify(PG_FUNCTION_ARGS)
static Datum coverage_window_calculation(PG_FUNCTION_ARGS, int mode)
Datum ST_ClusterKMeans(PG_FUNCTION_ARGS)
static cluster_context * fetch_cluster_context(WindowObject win_obj, uint32_t ngeoms)
Definition: lwgeom_window.c:63
static LWGEOM * read_lwgeom_from_partition(WindowObject win_obj, uint32_t i, bool *is_null)
Definition: lwgeom_window.c:71
Datum ST_ClusterDBSCAN(PG_FUNCTION_ARGS)
static void coverage_destroy_geoms(GEOSGeometry **geoms, uint32 ngeoms)
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
uint32_t * UF_get_collapsed_cluster_ids(UNIONFIND *uf, const char *is_in_cluster)
Definition: lwunionfind.c:146
void UF_destroy(UNIONFIND *uf)
Definition: lwunionfind.c:54
UNIONFIND * UF_create(uint32_t N)
Definition: lwunionfind.c:35
int value
Definition: genraster.py:62
GSERIALIZED * GEOS2POSTGIS(GEOSGeom geom, char want3d)
GEOSGeometry * POSTGIS2GEOS(const GSERIALIZED *pglwgeom)
#define HANDLE_GEOS_ERROR(label)
#define POSTGIS_GEOS_VERSION
Definition: sqldefines.h:11
cluster_entry clusters[1]
Definition: lwgeom_window.c:59
uint32_t cluster_id
Definition: lwgeom_window.c:50
char is_null
Definition: lwgeom_window.c:51
Definition: lwgeom_window.c:49
LWCOLLECTION * geom