PostGIS  3.7.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 #include "utils/builtins.h"
33 
34 /* PostGIS */
35 #include "liblwgeom.h"
36 #include "lwunionfind.h" /* TODO move into liblwgeom.h ? */
37 #include "lwgeom_geos.h"
38 #include "lwgeom_log.h"
39 #include "lwgeom_pg.h"
40 
41 
42 typedef struct {
43  bool isdone;
44  bool isnull;
45  int result[1];
46  /* variable length */
48 
49 typedef struct
50 {
51  uint32_t cluster_id;
52  char is_null; /* NULL may result from a NULL geometry input, or it may be used by
53  algorithms such as DBSCAN that do not assign all inputs to a
54  cluster. */
56 
57 typedef struct
58 {
59  char is_error;
60  cluster_entry clusters[1];
62 
63 static cluster_context*
64 fetch_cluster_context(WindowObject win_obj, uint32_t ngeoms)
65 {
66  size_t context_sz = sizeof(cluster_context) + (ngeoms * sizeof(cluster_entry));
67  cluster_context* context = WinGetPartitionLocalMemory(win_obj, context_sz);
68  return context;
69 }
70 
71 static LWGEOM*
72 read_lwgeom_from_partition(WindowObject win_obj, uint32_t i, bool* is_null)
73 {
74  GSERIALIZED* g;
75  Datum arg = WinGetFuncArgInPartition(win_obj, 0, i, WINDOW_SEEK_HEAD, false, is_null, NULL);
76 
77  if (*is_null) {
78  /* So that the indexes in our clustering input array can match our partition positions,
79  * toss an empty point into the clustering inputs, as a pass-through.
80  * NOTE: this will cause gaps in the output cluster id sequence.
81  * */
83  }
84 
85  g = (GSERIALIZED*) PG_DETOAST_DATUM_COPY(arg);
86  return lwgeom_from_gserialized(g);
87 }
88 
89 static GEOSGeometry*
90 read_geos_from_partition(WindowObject win_obj, uint32_t i, bool* is_null)
91 {
92  GSERIALIZED* g;
93  LWGEOM* lwg;
94  GEOSGeometry* gg;
95  Datum arg = WinGetFuncArgInPartition(win_obj, 0, i, WINDOW_SEEK_HEAD, false, is_null, NULL);
96 
97  if (*is_null) {
98  /* So that the indexes in our clustering input array can match our partition positions,
99  * toss an empty point into the clustering inputs, as a pass-through.
100  * NOTE: this will cause gaps in the output cluster id sequence.
101  * */
103  gg = LWGEOM2GEOS(lwg, LW_FALSE);
104  lwgeom_free(lwg);
105  return gg;
106  }
107 
108  g = (GSERIALIZED*) PG_DETOAST_DATUM_COPY(arg);
109  lwg = lwgeom_from_gserialized(g);
110  gg = LWGEOM2GEOS(lwg, LW_TRUE);
111  lwgeom_free(lwg);
112  if (!gg) {
113  *is_null = LW_TRUE;
114  }
115  return gg;
116 }
117 
118 extern Datum ST_ClusterDBSCAN(PG_FUNCTION_ARGS);
120 Datum ST_ClusterDBSCAN(PG_FUNCTION_ARGS)
121 {
122  WindowObject win_obj = PG_WINDOW_OBJECT();
123  uint32_t row = WinGetCurrentPosition(win_obj);
124  uint32_t ngeoms = WinGetPartitionRowCount(win_obj);
125  cluster_context* context = fetch_cluster_context(win_obj, ngeoms);
126 
127  if (row == 0) /* beginning of the partition; do all of the work now */
128  {
129  uint32_t i;
130  uint32_t* result_ids;
131  LWGEOM** geoms;
132  char* is_in_cluster = NULL;
133  UNIONFIND* uf;
134  bool tolerance_is_null;
135  bool minpoints_is_null;
136  Datum tolerance_datum = WinGetFuncArgCurrent(win_obj, 1, &tolerance_is_null);
137  Datum minpoints_datum = WinGetFuncArgCurrent(win_obj, 2, &minpoints_is_null);
138  double tolerance = DatumGetFloat8(tolerance_datum);
139  int minpoints = DatumGetInt32(minpoints_datum);
140 
141  context->is_error = LW_TRUE; /* until proven otherwise */
142 
143  /* Validate input parameters */
144  if (tolerance_is_null || tolerance < 0)
145  {
146  lwpgerror("Tolerance must be a positive number, got %g", tolerance);
147  PG_RETURN_NULL();
148  }
149  if (minpoints_is_null || minpoints < 0)
150  {
151  lwpgerror("Minpoints must be a positive number, got %d", minpoints);
152  }
153 
154  initGEOS(lwpgnotice, lwgeom_geos_error);
155  geoms = lwalloc(ngeoms * sizeof(LWGEOM*));
156  uf = UF_create(ngeoms);
157  for (i = 0; i < ngeoms; i++)
158  {
159  bool geom_is_null;
160  geoms[i] = read_lwgeom_from_partition(win_obj, i, &geom_is_null);
161  context->clusters[i].is_null = geom_is_null;
162 
163  if (!geoms[i]) {
164  /* TODO release memory ? */
165  lwpgerror("Error reading geometry.");
166  PG_RETURN_NULL();
167  }
168  }
169 
170  if (union_dbscan(geoms, ngeoms, uf, tolerance, minpoints, minpoints > 1 ? &is_in_cluster : NULL) == LW_SUCCESS)
171  context->is_error = LW_FALSE;
172 
173  for (i = 0; i < ngeoms; i++)
174  {
175  lwgeom_free(geoms[i]);
176  }
177  lwfree(geoms);
178 
179  if (context->is_error)
180  {
181  UF_destroy(uf);
182  if (is_in_cluster)
183  lwfree(is_in_cluster);
184  lwpgerror("Error during clustering");
185  PG_RETURN_NULL();
186  }
187 
188  result_ids = UF_get_collapsed_cluster_ids(uf, is_in_cluster);
189  for (i = 0; i < ngeoms; i++)
190  {
191  if (minpoints > 1 && !is_in_cluster[i])
192  {
193  context->clusters[i].is_null = LW_TRUE;
194  }
195  else
196  {
197  context->clusters[i].cluster_id = result_ids[i];
198  }
199  }
200 
201  lwfree(result_ids);
202  UF_destroy(uf);
203  }
204 
205  if (context->clusters[row].is_null)
206  PG_RETURN_NULL();
207 
208  PG_RETURN_INT32(context->clusters[row].cluster_id);
209 }
210 
211 extern Datum ST_ClusterWithinWin(PG_FUNCTION_ARGS);
213 Datum ST_ClusterWithinWin(PG_FUNCTION_ARGS)
214 {
215  WindowObject win_obj = PG_WINDOW_OBJECT();
216  uint32_t row = WinGetCurrentPosition(win_obj);
217  uint32_t ngeoms = WinGetPartitionRowCount(win_obj);
218  cluster_context* context = fetch_cluster_context(win_obj, ngeoms);
219 
220  if (row == 0) /* beginning of the partition; do all of the work now */
221  {
222  uint32_t i;
223  uint32_t* result_ids;
224  LWGEOM** geoms;
225  UNIONFIND* uf;
226  bool tolerance_is_null;
227  double tolerance = DatumGetFloat8(WinGetFuncArgCurrent(win_obj, 1, &tolerance_is_null));
228 
229  /* Validate input parameters */
230  if (tolerance_is_null || tolerance < 0)
231  {
232  lwpgerror("Tolerance must be a positive number, got %g", tolerance);
233  PG_RETURN_NULL();
234  }
235 
236  context->is_error = LW_TRUE; /* until proven otherwise */
237 
238  geoms = lwalloc(ngeoms * sizeof(LWGEOM*));
239  uf = UF_create(ngeoms);
240  for (i = 0; i < ngeoms; i++)
241  {
242  bool geom_is_null;
243  geoms[i] = read_lwgeom_from_partition(win_obj, i, &geom_is_null);
244  context->clusters[i].is_null = geom_is_null;
245 
246  if (!geoms[i])
247  {
248  lwpgerror("Error reading geometry.");
249  PG_RETURN_NULL();
250  }
251  }
252 
253  initGEOS(lwpgnotice, lwgeom_geos_error);
254 
255  if (union_dbscan(geoms, ngeoms, uf, tolerance, 1, NULL) == LW_SUCCESS)
256  context->is_error = LW_FALSE;
257 
258  for (i = 0; i < ngeoms; i++)
259  {
260  lwgeom_free(geoms[i]);
261  }
262  lwfree(geoms);
263 
264  if (context->is_error)
265  {
266  UF_destroy(uf);
267  lwpgerror("Error during clustering");
268  PG_RETURN_NULL();
269  }
270 
271  result_ids = UF_get_collapsed_cluster_ids(uf, NULL);
272  for (i = 0; i < ngeoms; i++)
273  {
274  context->clusters[i].cluster_id = result_ids[i];
275  }
276 
277  lwfree(result_ids);
278  UF_destroy(uf);
279  }
280 
281  if (context->clusters[row].is_null)
282  PG_RETURN_NULL();
283 
284  PG_RETURN_INT32(context->clusters[row].cluster_id);
285 }
286 
287 extern Datum ST_ClusterIntersectingWin(PG_FUNCTION_ARGS);
289 Datum ST_ClusterIntersectingWin(PG_FUNCTION_ARGS)
290 {
291  WindowObject win_obj = PG_WINDOW_OBJECT();
292  uint32_t row = WinGetCurrentPosition(win_obj);
293  uint32_t ngeoms = WinGetPartitionRowCount(win_obj);
294  cluster_context* context = fetch_cluster_context(win_obj, ngeoms);
295 
296  if (row == 0) /* beginning of the partition; do all of the work now */
297  {
298  uint32_t i;
299  uint32_t* result_ids;
300  GEOSGeometry** geoms = lwalloc(ngeoms * sizeof(GEOSGeometry*));
301  UNIONFIND* uf = UF_create(ngeoms);
302 
303  context->is_error = LW_TRUE; /* until proven otherwise */
304 
305  initGEOS(lwpgnotice, lwgeom_geos_error);
306 
307  for (i = 0; i < ngeoms; i++)
308  {
309  bool geom_is_null;
310  geoms[i] = read_geos_from_partition(win_obj, i, &geom_is_null);
311  context->clusters[i].is_null = geom_is_null;
312 
313  if (!geoms[i])
314  {
315  lwpgerror("Error reading geometry.");
316  PG_RETURN_NULL();
317  }
318  }
319 
320  if (union_intersecting_pairs(geoms, ngeoms, uf) == LW_SUCCESS)
321  context->is_error = LW_FALSE;
322 
323  for (i = 0; i < ngeoms; i++)
324  {
325  GEOSGeom_destroy(geoms[i]);
326  }
327  lwfree(geoms);
328 
329  if (context->is_error)
330  {
331  UF_destroy(uf);
332  lwpgerror("Error during clustering");
333  PG_RETURN_NULL();
334  }
335 
336  result_ids = UF_get_collapsed_cluster_ids(uf, NULL);
337  for (i = 0; i < ngeoms; i++)
338  {
339  context->clusters[i].cluster_id = result_ids[i];
340  }
341 
342  lwfree(result_ids);
343  UF_destroy(uf);
344  }
345 
346  if (context->clusters[row].is_null)
347  PG_RETURN_NULL();
348 
349  PG_RETURN_INT32(context->clusters[row].cluster_id);
350 }
351 
352 
353 extern Datum ST_ClusterKMeans(PG_FUNCTION_ARGS);
355 Datum ST_ClusterKMeans(PG_FUNCTION_ARGS)
356 {
357  WindowObject winobj = PG_WINDOW_OBJECT();
358  kmeans_context *context;
359  int64 curpos, rowcount;
360 
361  rowcount = WinGetPartitionRowCount(winobj);
362  context = (kmeans_context *)
363  WinGetPartitionLocalMemory(winobj,
364  sizeof(kmeans_context) + sizeof(int) * rowcount);
365 
366  if (!context->isdone)
367  {
368  int i, k, N;
369  bool isnull, isout;
370  double max_radius = 0.0;
371  LWGEOM **geoms;
372  int *r;
373  Datum argdatum;
374 
375  /* What is K? If it's NULL or invalid, we can't proceed */
376  argdatum = WinGetFuncArgCurrent(winobj, 1, &isnull);
377  k = DatumGetInt32(argdatum);
378  if (isnull || k <= 0)
379  {
380  context->isdone = true;
381  context->isnull = true;
382  PG_RETURN_NULL();
383  }
384 
385  /* We also need a non-zero N */
386  N = (int) WinGetPartitionRowCount(winobj);
387  if (N <= 0)
388  {
389  context->isdone = true;
390  context->isnull = true;
391  PG_RETURN_NULL();
392  }
393 
394  /* Maximum cluster radius. 0 if not set*/
395  argdatum = WinGetFuncArgCurrent(winobj, 2, &isnull);
396  if (!isnull)
397  {
398  max_radius = DatumGetFloat8(argdatum);
399  if (max_radius < 0)
400  max_radius = 0.0;
401  }
402 
403  /* Error out if N < K */
404  if (N<k)
405  lwpgerror("K (%d) must be smaller than the number of rows in the group (%d)", k, N);
406 
407  /* Read all the geometries from the partition window into a list */
408  geoms = palloc(sizeof(LWGEOM*) * N);
409  for (i = 0; i < N; i++)
410  {
411  GSERIALIZED *g;
412  Datum arg = WinGetFuncArgInPartition(winobj, 0, i,
413  WINDOW_SEEK_HEAD, false, &isnull, &isout);
414 
415  /* Null geometries are entered as NULL pointers */
416  if (isnull)
417  {
418  geoms[i] = NULL;
419  continue;
420  }
421 
422  g = (GSERIALIZED*)PG_DETOAST_DATUM_COPY(arg);
423  geoms[i] = lwgeom_from_gserialized(g);
424  }
425 
426  /* Calculate k-means on the list! */
427  r = lwgeom_cluster_kmeans((const LWGEOM **)geoms, N, k, max_radius);
428 
429  /* Clean up */
430  for (i = 0; i < N; i++)
431  if (geoms[i])
432  lwgeom_free(geoms[i]);
433 
434  pfree(geoms);
435 
436  if (!r)
437  {
438  context->isdone = true;
439  context->isnull = true;
440  PG_RETURN_NULL();
441  }
442 
443  /* Safe the result */
444  memcpy(context->result, r, sizeof(int) * N);
445  lwfree(r);
446  context->isdone = true;
447  }
448 
449  if (context->isnull)
450  PG_RETURN_NULL();
451 
452  curpos = WinGetCurrentPosition(winobj);
453  PG_RETURN_INT32(context->result[curpos]);
454 }
455 
456 
457 /********************************************************************************
458  * GEOS Coverage Functions
459  *
460  * The GEOS "coverage" support is a set of functions for working with
461  * "implicit coverages". That is, collections of polygonal geometry
462  * that have perfectly shared edges. Since perfectly matched edges are
463  * fast to detect, "building a coverage" on these inputs is fast, so
464  * operations like edge simplification and so on can include the "build"
465  * step with relatively low cost.
466  *
467  */
468 
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 
485 #if POSTGIS_GEOS_VERSION >= 31200
486 
487 /*
488  * For CoverageIsValid and CoverageSimplify, maintain context
489  * of unioned output and the position of the resultant in that
490  * output relative to the index of the input geometries.
491  */
492 typedef struct {
493  bool isdone;
494  bool isnull;
496  int64 idx[0];
497  /* variable length */
499 
500 /*
501  * For CoverageIsValid and CoverageSimplify, read the context
502  * out of the WindowObject.
503  */
504 static coverage_context *
505 coverage_context_fetch(WindowObject winobj, int64 rowcount)
506 {
507  size_t ctx_size = sizeof(coverage_context) + rowcount * sizeof(int64);
508  coverage_context *ctx = (coverage_context*) WinGetPartitionLocalMemory(
509  winobj, ctx_size);
510  return ctx;
511 }
512 
513 /*
514  * For CoverageIsValid and CoverageSimplify, read all the
515  * geometries in this partition and form a GEOS geometry
516  * collection out of them.
517  */
518 static GEOSGeometry*
520  WindowObject winobj,
521  coverage_context* context)
522 {
523  int64 rowcount = WinGetPartitionRowCount(winobj);
524  GEOSGeometry* geos;
525  GEOSGeometry** geoms;
526  uint32 i, ngeoms = 0, gtype;
527 
528  /* Read in all the geometries in this partition */
529  geoms = palloc(rowcount * sizeof(GEOSGeometry*));
530  for (i = 0; i < rowcount; i++)
531  {
532  GSERIALIZED* gser;
533  bool isnull, isout;
534  bool isempty, ispolygonal;
535  Datum d;
536 
537  /* Read geometry in first argument */
538  d = WinGetFuncArgInPartition(winobj, 0, i,
539  WINDOW_SEEK_HEAD, false, &isnull, &isout);
540 
541  /*
542  * The input to the GEOS function will be smaller than
543  * the input to this window, since we will not feed
544  * GEOS nulls or empties. So we need to maintain a
545  * map (context->idx) from the window position of the
546  * input to the GEOS position, so we can put the
547  * right result in the output stream. Things we want to
548  * skip get an index of -1.
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 geoms 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 {
619 #if POSTGIS_GEOS_VERSION >= 31400
620  ,COVERAGE_CLEAN = 2
621 #endif
622 };
623 
624 #if POSTGIS_GEOS_VERSION >= 31400
625 
626 static char * overlapMergeStrategies[] = {
627  /* Merge strategy that chooses polygon with longest common border */
628  "MERGE_LONGEST_BORDER",
629  /* Merge strategy that chooses polygon with maximum area */
630  "MERGE_MAX_AREA",
631  /* Merge strategy that chooses polygon with minimum area */
632  "MERGE_MIN_AREA",
633  /* Merge strategy that chooses polygon with smallest input index */
634  "MERGE_MIN_INDEX"
635 };
636 
637 static int
638 coverage_merge_strategy(const char *strategy)
639 {
640  size_t stratLen = sizeof(overlapMergeStrategies) / sizeof(overlapMergeStrategies[0]);
641  for (size_t i = 0; i < stratLen; i++)
642  {
643  if (strcasecmp(strategy, overlapMergeStrategies[i]) == 0)
644  {
645  return i;
646  }
647  }
648  return -1;
649 }
650 
651 #endif
652 
653 /*
654  * This calculation is shared by both coverage operations
655  * since they have the same pattern of "consume collection,
656  * return collection", and are both window functions.
657  */
658 static Datum
659 coverage_window_calculation(PG_FUNCTION_ARGS, int mode)
660 {
661  WindowObject winobj = PG_WINDOW_OBJECT();
662  int64 curpos = WinGetCurrentPosition(winobj);
663  int64 rowcount = WinGetPartitionRowCount(winobj);
664  coverage_context *context = coverage_context_fetch(winobj, rowcount);
666  MemoryContext uppercontext = fcinfo->flinfo->fn_mcxt;
667  MemoryContext oldcontext;
668  const LWGEOM* subgeom;
669 
670  if (!context->isdone)
671  {
672  bool isnull;
673  GEOSGeometry *output = NULL;
674  GEOSGeometry *input = NULL;
675  Datum d;
676 
677  if (!fcinfo->flinfo)
678  elog(ERROR, "%s: Could not find upper context", __func__);
679 
680  if (rowcount == 0)
681  {
682  context->isdone = true;
683  context->isnull = true;
684  PG_RETURN_NULL();
685  }
686 
687  initGEOS(lwpgnotice, lwgeom_geos_error);
688 
689  input = coverage_read_partition_into_collection(winobj, context);
690  if (!input)
691  HANDLE_GEOS_ERROR("Failed to create collection");
692 
693  /* Run the correct GEOS function for the calling mode */
694  if (mode == COVERAGE_SIMPLIFY)
695  {
696  bool simplifyBoundary = true;
697  double tolerance = 0.0;
698 
699  d = WinGetFuncArgCurrent(winobj, 1, &isnull);
700  if (!isnull) tolerance = DatumGetFloat8(d);
701 
702  d = WinGetFuncArgCurrent(winobj, 2, &isnull);
703  if (!isnull) simplifyBoundary = DatumGetFloat8(d);
704 
705  /* GEOSCoverageSimplifyVW is "preserveBoundary" so we invert simplifyBoundary */
706  output = GEOSCoverageSimplifyVW(input, tolerance, !simplifyBoundary);
707  }
708  else if (mode == COVERAGE_ISVALID)
709  {
710  double tolerance = 0.0;
711  d = WinGetFuncArgCurrent(winobj, 1, &isnull);
712  if (!isnull) tolerance = DatumGetFloat8(d);
713  GEOSCoverageIsValid(input, tolerance, &output);
714  }
715 
716 #if POSTGIS_GEOS_VERSION >= 31400
717 
718  else if (mode == COVERAGE_CLEAN)
719  {
720  double snappingDistance = 0.0;
721  double gapMaximumWidth = 0.0;
722  text *overlapMergeStrategyText;
723  int overlapMergeStrategy;
724  GEOSCoverageCleanParams *params = NULL;
725 
726  d = WinGetFuncArgCurrent(winobj, 1, &isnull);
727  if (!isnull) gapMaximumWidth = DatumGetFloat8(d);
728 
729  d = WinGetFuncArgCurrent(winobj, 2, &isnull);
730  if (!isnull) snappingDistance = DatumGetFloat8(d);
731 
732  d = WinGetFuncArgCurrent(winobj, 3, &isnull);
733  if (!isnull)
734  {
735  overlapMergeStrategyText = DatumGetTextP(d);
736  overlapMergeStrategy = coverage_merge_strategy(text_to_cstring(overlapMergeStrategyText));
737  }
738  else
739  {
740  overlapMergeStrategy = 0; /* Default to MERGE_LONGEST_BORDER */
741  }
742  if (overlapMergeStrategy < 0)
743  {
744  HANDLE_GEOS_ERROR("Invalid OverlapMergeStrategy");
745  }
746 
747  params = GEOSCoverageCleanParams_create();
748  GEOSCoverageCleanParams_setGapMaximumWidth(params, gapMaximumWidth);
749  GEOSCoverageCleanParams_setSnappingDistance(params, snappingDistance);
750  if (!GEOSCoverageCleanParams_setOverlapMergeStrategy(params, overlapMergeStrategy))
751  {
752  GEOSCoverageCleanParams_destroy(params);
753  HANDLE_GEOS_ERROR("Invalid OverlapMergeStrategy");
754  }
755 
756  output = GEOSCoverageCleanWithParams(input, params);
757  GEOSCoverageCleanParams_destroy(params);
758  }
759 #endif
760  else
761  {
762  elog(ERROR, "Unknown mode, never get here");
763  }
764 
765  GEOSGeom_destroy(input);
766 
767  if (!output)
768  {
769  HANDLE_GEOS_ERROR("Failed to process collection");
770  }
771 
772  oldcontext = MemoryContextSwitchTo(uppercontext);
773  context->geom = (LWCOLLECTION *) GEOS2LWGEOM(output, GEOSHasZ(output));
774  MemoryContextSwitchTo(oldcontext);
775  GEOSGeom_destroy(output);
776 
777  context->isdone = true;
778  }
779 
780  /* Bomb out of any errors */
781  if (context->isnull)
782  PG_RETURN_NULL();
783 
784  /* Propagate the null entries */
785  if (context->idx[curpos] < 0)
786  PG_RETURN_NULL();
787 
788  /*
789  * Geometry serialization is not const-safe! (we
790  * calculate bounding boxes on demand) so we need
791  * to make sure we have a persistent context when
792  * we call the serialization, lest we create dangling
793  * pointers in the object. It's possible we could
794  * ignore them, skip the manual lwcollection_free
795  * and let the aggcontext deletion take
796  * care of the memory.
797  */
798  oldcontext = MemoryContextSwitchTo(uppercontext);
799  subgeom = lwcollection_getsubgeom(context->geom, context->idx[curpos]);
800  if (mode == COVERAGE_ISVALID && lwgeom_is_empty(subgeom))
801  {
802  result = NULL;
803  }
804  else
805  {
806  result = geometry_serialize((LWGEOM*)subgeom);
807  }
808  MemoryContextSwitchTo(oldcontext);
809 
810  /* When at the end of the partition, release the */
811  /* simplified collection we have been reading. */
812  if (curpos == rowcount - 1)
813  lwcollection_free(context->geom);
814 
815  if (!result) PG_RETURN_NULL();
816 
817  PG_RETURN_POINTER(result);
818 
819 }
820 #endif /* POSTGIS_GEOS_VERSION >= 31200 */
821 
822 
823 extern Datum ST_CoverageSimplify(PG_FUNCTION_ARGS);
825 Datum ST_CoverageSimplify(PG_FUNCTION_ARGS)
826 {
827 #if POSTGIS_GEOS_VERSION < 31200
828 
829  lwpgerror("The GEOS version this PostGIS binary "
830  "was compiled against (%d) not include "
831  "'GEOSCoverageSimplifyVW' function (3.12 or greater required)",
833  PG_RETURN_NULL();
834 
835 #else /* POSTGIS_GEOS_VERSION >= 31200 */
836 
838 
839 #endif
840 }
841 
842 
843 extern Datum ST_CoverageInvalidEdges(PG_FUNCTION_ARGS);
845 Datum ST_CoverageInvalidEdges(PG_FUNCTION_ARGS)
846 {
847 #if POSTGIS_GEOS_VERSION < 31200
848 
849  lwpgerror("The GEOS version this PostGIS binary "
850  "was compiled against (%d) does not include "
851  "'GEOSCoverageIsValid' function (3.12 or greater required)",
853  PG_RETURN_NULL();
854 
855 #else /* POSTGIS_GEOS_VERSION >= 31200 */
856 
858 
859 #endif
860 }
861 
862 extern Datum ST_CoverageClean(PG_FUNCTION_ARGS);
864 Datum ST_CoverageClean(PG_FUNCTION_ARGS)
865 {
866 #if POSTGIS_GEOS_VERSION < 31400
867 
868  lwpgerror("The GEOS version this PostGIS binary "
869  "was compiled against (%d) not include "
870  "'GEOSCoverageClean' function (3.14 or greater required)",
872  PG_RETURN_NULL();
873 
874 #else /* POSTGIS_GEOS_VERSION >= 31400 */
875 
876  return coverage_window_calculation(fcinfo, COVERAGE_CLEAN);
877 
878 #endif
879 }
880 
881 /**********************************************************************
882  * ST_CoverageUnion(geometry[])
883  *
884  */
885 
886 Datum ST_CoverageUnion(PG_FUNCTION_ARGS);
888 Datum ST_CoverageUnion(PG_FUNCTION_ARGS)
889 {
890  GSERIALIZED *result = NULL;
891 
892  Datum value;
893  bool isnull;
894 
895  GEOSGeometry **geoms = NULL;
896  GEOSGeometry *geos = NULL;
897  GEOSGeometry *geos_result = NULL;
898  uint32 ngeoms = 0;
899 
900  ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
901  uint32 nelems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
902  ArrayIterator iterator = array_create_iterator(array, 0, NULL);
903 
904  /* Return null on 0-elements input array */
905  if (nelems == 0)
906  PG_RETURN_NULL();
907 
908  /* Convert all geometries into GEOSGeometry array */
909  geoms = palloc(sizeof(GEOSGeometry *) * nelems);
910 
911  initGEOS(lwpgnotice, lwgeom_geos_error);
912 
913  while (array_iterate(iterator, &value, &isnull))
914  {
915  GSERIALIZED *gser;
916  /* Omit nulls */
917  if (isnull) continue;
918 
919  /* Omit empty */
920  gser = (GSERIALIZED *)DatumGetPointer(value);
921  if (gserialized_is_empty(gser)) continue;
922 
923  /* Omit unconvertible */
924  geos = POSTGIS2GEOS(gser);
925  if (!geos) continue;
926 
927  geoms[ngeoms++] = geos;
928  }
929  array_free_iterator(iterator);
930 
931  if (ngeoms == 0)
932  PG_RETURN_NULL();
933 
934  geos = GEOSGeom_createCollection(
935  GEOS_GEOMETRYCOLLECTION,
936  geoms, ngeoms);
937 
938  if (!geos)
939  {
940  coverage_destroy_geoms(geoms, ngeoms);
941  HANDLE_GEOS_ERROR("Geometry could not be converted");
942  }
943 
944  geos_result = GEOSCoverageUnion(geos);
945  GEOSGeom_destroy(geos);
946  if (!geos_result)
947  HANDLE_GEOS_ERROR("Error computing coverage union");
948 
949  result = GEOS2POSTGIS(geos_result, LW_FALSE);
950  GEOSGeom_destroy(geos_result);
951 
952  PG_RETURN_POINTER(result);
953 }
954 
955 
char * r
Definition: cu_in_wkt.c:24
char result[OUT_DOUBLE_BUFFER_SIZE]
Definition: cu_print.c:267
LWGEOM * lwgeom_from_gserialized(const GSERIALIZED *g)
Allocate a new LWGEOM from a GSERIALIZED.
Definition: gserialized.c:268
int gserialized_is_empty(const GSERIALIZED *g)
Check if a GSERIALIZED is empty without deserializing first.
Definition: gserialized.c:181
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:118
GEOSGeometry * LWGEOM2GEOS(const LWGEOM *lwgeom, uint8_t autofix)
void lwgeom_geos_error(const char *fmt,...)
void(*) LWGEOM GEOS2LWGEOM)(const GEOSGeometry *geom, uint8_t want3d)
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:1218
#define LW_SUCCESS
Definition: liblwgeom.h:97
const LWGEOM * lwcollection_getsubgeom(LWCOLLECTION *col, uint32_t gnum)
Definition: lwcollection.c:115
#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:248
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.
Datum ST_CoverageClean(PG_FUNCTION_ARGS)
@ 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:90
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:64
static LWGEOM * read_lwgeom_from_partition(WindowObject win_obj, uint32_t i, bool *is_null)
Definition: lwgeom_window.c:72
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:199
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:60
uint32_t cluster_id
Definition: lwgeom_window.c:51
char is_null
Definition: lwgeom_window.c:52
Definition: lwgeom_window.c:50
LWCOLLECTION * geom