PostGIS  3.1.6dev-r@@SVN_REVISION@@
liblwgeom/lwgeom_geos_clean.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 2009-2010 Sandro Santilli <strk@kbt.io>
22  *
23  **********************************************************************/
24 
25 #include "../postgis_config.h"
26 /*#define POSTGIS_DEBUG_LEVEL 4*/
27 
28 #include "liblwgeom.h"
29 #include "lwgeom_geos.h"
30 #include "liblwgeom_internal.h"
31 #include "lwgeom_log.h"
32 
33 #include <string.h>
34 #include <stdlib.h>
35 #include <assert.h>
36 
37 /* #define PARANOIA_LEVEL 2 */
38 #undef LWGEOM_PROFILE_MAKEVALID
39 
40 #if POSTGIS_GEOS_VERSION < 38
41 /*
42  * Return Nth vertex in GEOSGeometry as a POINT.
43  * May return NULL if the geometry has NO vertex.
44  */
45 GEOSGeometry* LWGEOM_GEOS_getPointN(const GEOSGeometry*, uint32_t);
46 GEOSGeometry*
47 LWGEOM_GEOS_getPointN(const GEOSGeometry* g_in, uint32_t n)
48 {
49  uint32_t dims = 0;
50  const GEOSCoordSequence* seq_in;
51  GEOSCoordSeq seq_out;
52  double val;
53  uint32_t sz = 0;
54  int gn;
55  GEOSGeometry* ret;
56 
57  switch (GEOSGeomTypeId(g_in))
58  {
59  case GEOS_MULTIPOINT:
60  case GEOS_MULTILINESTRING:
61  case GEOS_MULTIPOLYGON:
62  case GEOS_GEOMETRYCOLLECTION:
63  {
64  for (gn = 0; gn < GEOSGetNumGeometries(g_in); ++gn)
65  {
66  const GEOSGeometry* g = GEOSGetGeometryN(g_in, gn);
67  ret = LWGEOM_GEOS_getPointN(g, n);
68  if (ret) return ret;
69  }
70  break;
71  }
72 
73  case GEOS_POLYGON:
74  {
75  ret = LWGEOM_GEOS_getPointN(GEOSGetExteriorRing(g_in), n);
76  if (ret) return ret;
77  for (gn = 0; gn < GEOSGetNumInteriorRings(g_in); ++gn)
78  {
79  const GEOSGeometry* g = GEOSGetInteriorRingN(g_in, gn);
80  ret = LWGEOM_GEOS_getPointN(g, n);
81  if (ret) return ret;
82  }
83  break;
84  }
85 
86  case GEOS_POINT:
87  case GEOS_LINESTRING:
88  case GEOS_LINEARRING:
89  break;
90  }
91 
92  seq_in = GEOSGeom_getCoordSeq(g_in);
93  if (!seq_in) return NULL;
94  if (!GEOSCoordSeq_getSize(seq_in, &sz)) return NULL;
95  if (!sz) return NULL;
96 
97  if (!GEOSCoordSeq_getDimensions(seq_in, &dims)) return NULL;
98 
99  seq_out = GEOSCoordSeq_create(1, dims);
100  if (!seq_out) return NULL;
101 
102  if (!GEOSCoordSeq_getX(seq_in, n, &val)) return NULL;
103  if (!GEOSCoordSeq_setX(seq_out, n, val)) return NULL;
104  if (!GEOSCoordSeq_getY(seq_in, n, &val)) return NULL;
105  if (!GEOSCoordSeq_setY(seq_out, n, val)) return NULL;
106  if (dims > 2)
107  {
108  if (!GEOSCoordSeq_getZ(seq_in, n, &val)) return NULL;
109  if (!GEOSCoordSeq_setZ(seq_out, n, val)) return NULL;
110  }
111 
112  return GEOSGeom_createPoint(seq_out);
113 }
114 #endif
115 
120 
121 static void
123 {
124  uint32_t i, j = 0;
125  POINT4D *p, *np;
126  int ndims = FLAGS_NDIMS(pa->flags);
127  for ( i = 0; i < pa->npoints; i++ )
128  {
129  int isnan = 0;
130  p = (POINT4D *)(getPoint_internal(pa, i));
131  if ( isnan(p->x) || isnan(p->y) ) isnan = 1;
132  else if (ndims > 2 && isnan(p->z) ) isnan = 1;
133  else if (ndims > 3 && isnan(p->m) ) isnan = 1;
134  if ( isnan ) continue;
135 
136  np = (POINT4D *)(getPoint_internal(pa, j++));
137  if ( np != p ) {
138  np->x = p->x;
139  np->y = p->y;
140  if (ndims > 2)
141  np->z = p->z;
142  if (ndims > 3)
143  np->m = p->m;
144  }
145  }
146  pa->npoints = j;
147 }
148 
149 
150 
151 /*
152  * Ensure the geometry is "structurally" valid
153  * (enough for GEOS to accept it)
154  * May return the input untouched (if already valid).
155  * May return geometries of lower dimension (on collapses)
156  */
157 static LWGEOM*
159 {
160  LWDEBUGF(2, "lwgeom_make_geos_friendly enter (type %d)", geom->type);
161  switch (geom->type)
162  {
163  case POINTTYPE:
165  return geom;
166 
167  case LINETYPE:
168  /* lines need at least 2 points */
169  return lwline_make_geos_friendly((LWLINE*)geom);
170  break;
171 
172  case POLYGONTYPE:
173  /* polygons need all rings closed and with npoints > 3 */
174  return lwpoly_make_geos_friendly((LWPOLY*)geom);
175  break;
176 
177  case MULTILINETYPE:
178  case MULTIPOLYGONTYPE:
179  case COLLECTIONTYPE:
180  case MULTIPOINTTYPE:
182  break;
183 
184  case CIRCSTRINGTYPE:
185  case COMPOUNDTYPE:
186  case CURVEPOLYTYPE:
187  case MULTISURFACETYPE:
188  case MULTICURVETYPE:
189  default:
190  lwerror("lwgeom_make_geos_friendly: unsupported input geometry type: %s (%d)",
191  lwtype_name(geom->type),
192  geom->type);
193  break;
194  }
195  return 0;
196 }
197 
198 /*
199  * Close the point array, if not already closed in 2d.
200  * Returns the input if already closed in 2d, or a newly
201  * constructed POINTARRAY.
202  * TODO: move in ptarray.c
203  */
204 static POINTARRAY*
206 {
207  POINTARRAY* newring;
208 
209  /* close the ring if not already closed (2d only) */
210  if (!ptarray_is_closed_2d(ring))
211  {
212  /* close it up */
213  newring = ptarray_addPoint(ring, getPoint_internal(ring, 0), FLAGS_NDIMS(ring->flags), ring->npoints);
214  ring = newring;
215  }
216  return ring;
217 }
218 
219 /* May return the same input or a new one (never zero) */
220 POINTARRAY*
222 {
223  POINTARRAY* closedring;
224  POINTARRAY* ring_in = ring;
225 
227 
228  /* close the ring if not already closed (2d only) */
229  closedring = ptarray_close2d(ring);
230  if (closedring != ring) ring = closedring;
231 
232  /* return 0 for collapsed ring (after closeup) */
233 
234  while (ring->npoints < 4)
235  {
236  POINTARRAY* oring = ring;
237  LWDEBUGF(4, "ring has %d points, adding another", ring->npoints);
238  /* let's add another... */
239  ring = ptarray_addPoint(ring, getPoint_internal(ring, 0), FLAGS_NDIMS(ring->flags), ring->npoints);
240  if (oring != ring_in) ptarray_free(oring);
241  }
242 
243  return ring;
244 }
245 
246 /* Make sure all rings are closed and have > 3 points.
247  * May return the input untouched.
248  */
249 LWGEOM*
251 {
252  LWGEOM* ret;
253  POINTARRAY** new_rings;
254  uint32_t i;
255 
256  /* If the polygon has no rings there's nothing to do */
257  if (!poly->nrings) return (LWGEOM*)poly;
258 
259  /* Allocate enough pointers for all rings */
260  new_rings = lwalloc(sizeof(POINTARRAY*) * poly->nrings);
261 
262  /* All rings must be closed and have > 3 points */
263  for (i = 0; i < poly->nrings; i++)
264  {
265  POINTARRAY* ring_in = poly->rings[i];
266  POINTARRAY* ring_out = ring_make_geos_friendly(ring_in);
267 
268  if (ring_in != ring_out)
269  {
270  LWDEBUGF(
271  3, "lwpoly_make_geos_friendly: ring %d cleaned, now has %d points", i, ring_out->npoints);
272  ptarray_free(ring_in);
273  }
274  else
275  LWDEBUGF(3, "lwpoly_make_geos_friendly: ring %d untouched", i);
276 
277  assert(ring_out);
278  new_rings[i] = ring_out;
279  }
280 
281  lwfree(poly->rings);
282  poly->rings = new_rings;
283  ret = (LWGEOM*)poly;
284 
285  return ret;
286 }
287 
288 /* Need NO or >1 points. Duplicate first if only one. */
289 LWGEOM*
291 {
292  LWGEOM* ret;
293 
295 
296  if (line->points->npoints == 1) /* 0 is fine, 2 is fine */
297  {
298 #if 1
299  /* Duplicate point */
300  line->points = ptarray_addPoint(line->points,
301  getPoint_internal(line->points, 0),
302  FLAGS_NDIMS(line->points->flags),
303  line->points->npoints);
304  ret = (LWGEOM*)line;
305 #else
306  /* Turn into a point */
307  ret = (LWGEOM*)lwpoint_construct(line->srid, 0, line->points);
308 #endif
309  return ret;
310  }
311  else
312  {
313  return (LWGEOM*)line;
314  /* return lwline_clone(line); */
315  }
316 }
317 
318 LWGEOM*
320 {
321  LWGEOM** new_geoms;
322  uint32_t i, new_ngeoms = 0;
323  LWCOLLECTION* ret;
324 
325  if ( ! g->ngeoms ) {
326  LWDEBUG(3, "lwcollection_make_geos_friendly: returning input untouched");
327  return lwcollection_as_lwgeom(g);
328  }
329 
330  /* enough space for all components */
331  new_geoms = lwalloc(sizeof(LWGEOM*) * g->ngeoms);
332 
333  ret = lwalloc(sizeof(LWCOLLECTION));
334  memcpy(ret, g, sizeof(LWCOLLECTION));
335  ret->maxgeoms = g->ngeoms;
336 
337  for (i = 0; i < g->ngeoms; i++)
338  {
339  LWGEOM* newg = lwgeom_make_geos_friendly(g->geoms[i]);
340  if (!newg) continue;
341  if ( newg != g->geoms[i] ) {
342  new_geoms[new_ngeoms++] = newg;
343  } else {
344  new_geoms[new_ngeoms++] = lwgeom_clone(newg);
345  }
346  }
347 
348  ret->bbox = NULL; /* recompute later... */
349 
350  ret->ngeoms = new_ngeoms;
351  if (new_ngeoms)
352  ret->geoms = new_geoms;
353  else
354  {
355  free(new_geoms);
356  ret->geoms = NULL;
357  ret->maxgeoms = 0;
358  }
359 
360  return (LWGEOM*)ret;
361 }
362 
363 #if POSTGIS_GEOS_VERSION < 38
364 
365 /*
366  * Fully node given linework
367  */
368 static GEOSGeometry*
369 LWGEOM_GEOS_nodeLines(const GEOSGeometry* lines)
370 {
371  /* GEOS3.7 GEOSNode fails on regression tests */
372  /* GEOS3.7 GEOSUnaryUnion fails on regression tests */
373 
374  /* union of first point with geometry */
375  GEOSGeometry *unioned, *point;
376  point = LWGEOM_GEOS_getPointN(lines, 0);
377  if (!point) return NULL;
378  unioned = GEOSUnion(lines, point);
379  GEOSGeom_destroy(point);
380  return unioned;
381 }
382 
383 /*
384  * We expect initGEOS being called already.
385  * Will return NULL on error (expect error handler being called by then)
386  */
387 static GEOSGeometry*
388 LWGEOM_GEOS_makeValidPolygon(const GEOSGeometry* gin)
389 {
390  GEOSGeom gout;
391  GEOSGeom geos_bound;
392  GEOSGeom geos_cut_edges, geos_area, collapse_points;
393  GEOSGeometry* vgeoms[3]; /* One for area, one for cut-edges */
394  unsigned int nvgeoms = 0;
395 
396  assert(GEOSGeomTypeId(gin) == GEOS_POLYGON || GEOSGeomTypeId(gin) == GEOS_MULTIPOLYGON);
397 
398  geos_bound = GEOSBoundary(gin);
399  if (!geos_bound) return NULL;
400 
401  /* Use noded boundaries as initial "cut" edges */
402 
403  geos_cut_edges = LWGEOM_GEOS_nodeLines(geos_bound);
404  if (!geos_cut_edges)
405  {
406  GEOSGeom_destroy(geos_bound);
407  lwnotice("LWGEOM_GEOS_nodeLines(): %s", lwgeom_geos_errmsg);
408  return NULL;
409  }
410 
411  /* NOTE: the noding process may drop lines collapsing to points.
412  * We want to retrieve any of those */
413  {
414  GEOSGeometry* pi;
415  GEOSGeometry* po;
416 
417  pi = GEOSGeom_extractUniquePoints(geos_bound);
418  if (!pi)
419  {
420  GEOSGeom_destroy(geos_bound);
421  lwnotice("GEOSGeom_extractUniquePoints(): %s", lwgeom_geos_errmsg);
422  return NULL;
423  }
424 
425  po = GEOSGeom_extractUniquePoints(geos_cut_edges);
426  if (!po)
427  {
428  GEOSGeom_destroy(geos_bound);
429  GEOSGeom_destroy(pi);
430  lwnotice("GEOSGeom_extractUniquePoints(): %s", lwgeom_geos_errmsg);
431  return NULL;
432  }
433 
434  collapse_points = GEOSDifference(pi, po);
435  if (!collapse_points)
436  {
437  GEOSGeom_destroy(geos_bound);
438  GEOSGeom_destroy(pi);
439  GEOSGeom_destroy(po);
440  lwnotice("GEOSDifference(): %s", lwgeom_geos_errmsg);
441  return NULL;
442  }
443 
444  GEOSGeom_destroy(pi);
445  GEOSGeom_destroy(po);
446  }
447  GEOSGeom_destroy(geos_bound);
448 
449  /* And use an empty geometry as initial "area" */
450  geos_area = GEOSGeom_createEmptyPolygon();
451  if (!geos_area)
452  {
453  lwnotice("GEOSGeom_createEmptyPolygon(): %s", lwgeom_geos_errmsg);
454  GEOSGeom_destroy(geos_cut_edges);
455  return NULL;
456  }
457 
458  /*
459  * See if an area can be build with the remaining edges
460  * and if it can, symdifference with the original area.
461  * Iterate this until no more polygons can be created
462  * with left-over edges.
463  */
464  while (GEOSGetNumGeometries(geos_cut_edges))
465  {
466  GEOSGeometry* new_area = 0;
467  GEOSGeometry* new_area_bound = 0;
468  GEOSGeometry* symdif = 0;
469  GEOSGeometry* new_cut_edges = 0;
470 
471 #ifdef LWGEOM_PROFILE_MAKEVALID
472  lwnotice("ST_MakeValid: building area from %d edges", GEOSGetNumGeometries(geos_cut_edges));
473 #endif
474 
475  /*
476  * ASSUMPTION: cut_edges should already be fully noded
477  */
478 
479  new_area = LWGEOM_GEOS_buildArea(geos_cut_edges);
480  if (!new_area) /* must be an exception */
481  {
482  GEOSGeom_destroy(geos_cut_edges);
483  GEOSGeom_destroy(geos_area);
484  lwnotice("LWGEOM_GEOS_buildArea() threw an error: %s", lwgeom_geos_errmsg);
485  return NULL;
486  }
487 
488  if (GEOSisEmpty(new_area))
489  {
490  /* no more rings can be build with thes edges */
491  GEOSGeom_destroy(new_area);
492  break;
493  }
494 
495  /*
496  * We succeeded in building a ring!
497  * Save the new ring boundaries first (to compute
498  * further cut edges later)
499  */
500  new_area_bound = GEOSBoundary(new_area);
501  if (!new_area_bound)
502  {
503  /* We did check for empty area already so this must be some other error */
504  lwnotice("GEOSBoundary('%s') threw an error: %s",
505  lwgeom_to_ewkt(GEOS2LWGEOM(new_area, 0)),
507  GEOSGeom_destroy(new_area);
508  GEOSGeom_destroy(geos_area);
509  return NULL;
510  }
511 
512  /*
513  * Now symdif new and old area
514  */
515  symdif = GEOSSymDifference(geos_area, new_area);
516  if (!symdif) /* must be an exception */
517  {
518  GEOSGeom_destroy(geos_cut_edges);
519  GEOSGeom_destroy(new_area);
520  GEOSGeom_destroy(new_area_bound);
521  GEOSGeom_destroy(geos_area);
522  lwnotice("GEOSSymDifference() threw an error: %s", lwgeom_geos_errmsg);
523  return NULL;
524  }
525 
526  GEOSGeom_destroy(geos_area);
527  GEOSGeom_destroy(new_area);
528  geos_area = symdif;
529  symdif = 0;
530 
531  /*
532  * Now let's re-set geos_cut_edges with what's left
533  * from the original boundary.
534  * ASSUMPTION: only the previous cut-edges can be
535  * left, so we don't need to reconsider
536  * the whole original boundaries
537  *
538  * NOTE: this is an expensive operation.
539  *
540  */
541 
542  new_cut_edges = GEOSDifference(geos_cut_edges, new_area_bound);
543  GEOSGeom_destroy(new_area_bound);
544  if (!new_cut_edges) /* an exception ? */
545  {
546  /* cleanup and throw */
547  GEOSGeom_destroy(geos_cut_edges);
548  GEOSGeom_destroy(geos_area);
549  lwerror("GEOSDifference() threw an error: %s", lwgeom_geos_errmsg);
550  return NULL;
551  }
552  GEOSGeom_destroy(geos_cut_edges);
553  geos_cut_edges = new_cut_edges;
554  }
555 
556  if (!GEOSisEmpty(geos_area))
557  vgeoms[nvgeoms++] = geos_area;
558  else
559  GEOSGeom_destroy(geos_area);
560 
561  if (!GEOSisEmpty(geos_cut_edges))
562  vgeoms[nvgeoms++] = geos_cut_edges;
563  else
564  GEOSGeom_destroy(geos_cut_edges);
565 
566  if (!GEOSisEmpty(collapse_points))
567  vgeoms[nvgeoms++] = collapse_points;
568  else
569  GEOSGeom_destroy(collapse_points);
570 
571  if (1 == nvgeoms)
572  {
573  /* Return cut edges */
574  gout = vgeoms[0];
575  }
576  else
577  {
578  /* Collect areas and lines (if any line) */
579  gout = GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION, vgeoms, nvgeoms);
580  if (!gout) /* an exception again */
581  {
582  /* cleanup and throw */
583  lwerror("GEOSGeom_createCollection() threw an error: %s", lwgeom_geos_errmsg);
584  /* TODO: cleanup! */
585  return NULL;
586  }
587  }
588 
589  return gout;
590 }
591 
592 static GEOSGeometry*
593 LWGEOM_GEOS_makeValidLine(const GEOSGeometry* gin)
594 {
595  GEOSGeometry* noded;
596  noded = LWGEOM_GEOS_nodeLines(gin);
597  return noded;
598 }
599 
600 static GEOSGeometry*
601 LWGEOM_GEOS_makeValidMultiLine(const GEOSGeometry* gin)
602 {
603  GEOSGeometry** lines;
604  GEOSGeometry** points;
605  GEOSGeometry* mline_out = 0;
606  GEOSGeometry* mpoint_out = 0;
607  GEOSGeometry* gout = 0;
608  uint32_t nlines = 0, nlines_alloc;
609  uint32_t npoints = 0;
610  uint32_t ngeoms = 0, nsubgeoms;
611  uint32_t i, j;
612 
613  ngeoms = GEOSGetNumGeometries(gin);
614 
615  nlines_alloc = ngeoms;
616  lines = lwalloc(sizeof(GEOSGeometry*) * nlines_alloc);
617  points = lwalloc(sizeof(GEOSGeometry*) * ngeoms);
618 
619  for (i = 0; i < ngeoms; ++i)
620  {
621  const GEOSGeometry* g = GEOSGetGeometryN(gin, i);
622  GEOSGeometry* vg;
623  vg = LWGEOM_GEOS_makeValidLine(g);
624  /* Drop any invalid or empty geometry */
625  if (!vg)
626  continue;
627  if (GEOSisEmpty(vg))
628  {
629  GEOSGeom_destroy(vg);
630  continue;
631  }
632 
633  if (GEOSGeomTypeId(vg) == GEOS_POINT)
634  points[npoints++] = vg;
635  else if (GEOSGeomTypeId(vg) == GEOS_LINESTRING)
636  lines[nlines++] = vg;
637  else if (GEOSGeomTypeId(vg) == GEOS_MULTILINESTRING)
638  {
639  nsubgeoms = GEOSGetNumGeometries(vg);
640  nlines_alloc += nsubgeoms;
641  lines = lwrealloc(lines, sizeof(GEOSGeometry*) * nlines_alloc);
642  for (j = 0; j < nsubgeoms; ++j)
643  {
644  const GEOSGeometry* gc = GEOSGetGeometryN(vg, j);
645  /* NOTE: ownership of the cloned geoms will be
646  * taken by final collection */
647  lines[nlines++] = GEOSGeom_clone(gc);
648  }
649  }
650  else
651  {
652  /* NOTE: return from GEOSGeomType will leak
653  * but we really don't expect this to happen */
654  lwerror("unexpected geom type returned by LWGEOM_GEOS_makeValid: %s", GEOSGeomType(vg));
655  }
656  }
657 
658  if (npoints)
659  {
660  if (npoints > 1)
661  mpoint_out = GEOSGeom_createCollection(GEOS_MULTIPOINT, points, npoints);
662  else
663  mpoint_out = points[0];
664  }
665 
666  if (nlines)
667  {
668  if (nlines > 1)
669  mline_out = GEOSGeom_createCollection(GEOS_MULTILINESTRING, lines, nlines);
670  else
671  mline_out = lines[0];
672  }
673 
674  lwfree(lines);
675 
676  if (mline_out && mpoint_out)
677  {
678  points[0] = mline_out;
679  points[1] = mpoint_out;
680  gout = GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION, points, 2);
681  }
682  else if (mline_out)
683  gout = mline_out;
684 
685  else if (mpoint_out)
686  gout = mpoint_out;
687 
688  lwfree(points);
689 
690  return gout;
691 }
692 
693 /*
694  * We expect initGEOS being called already.
695  * Will return NULL on error (expect error handler being called by then)
696  */
697 static GEOSGeometry*
698 LWGEOM_GEOS_makeValidCollection(const GEOSGeometry* gin)
699 {
700  int nvgeoms;
701  GEOSGeometry** vgeoms;
702  GEOSGeom gout;
703  int i;
704 
705  nvgeoms = GEOSGetNumGeometries(gin);
706  if (nvgeoms == -1)
707  {
708  lwerror("GEOSGetNumGeometries: %s", lwgeom_geos_errmsg);
709  return 0;
710  }
711 
712  vgeoms = lwalloc(sizeof(GEOSGeometry*) * nvgeoms);
713  if (!vgeoms)
714  {
715  lwerror("LWGEOM_GEOS_makeValidCollection: out of memory");
716  return 0;
717  }
718 
719  for (i = 0; i < nvgeoms; ++i)
720  {
721  vgeoms[i] = LWGEOM_GEOS_makeValid(GEOSGetGeometryN(gin, i));
722  if (!vgeoms[i])
723  {
724  int j;
725  for (j = 0; j < i - 1; j++)
726  GEOSGeom_destroy(vgeoms[j]);
727  lwfree(vgeoms);
728  /* we expect lwerror being called already by makeValid */
729  return NULL;
730  }
731  }
732 
733  /* Collect areas and lines (if any line) */
734  gout = GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION, vgeoms, nvgeoms);
735  if (!gout) /* an exception again */
736  {
737  /* cleanup and throw */
738  for (i = 0; i < nvgeoms; ++i)
739  GEOSGeom_destroy(vgeoms[i]);
740  lwfree(vgeoms);
741  lwerror("GEOSGeom_createCollection() threw an error: %s", lwgeom_geos_errmsg);
742  return NULL;
743  }
744  lwfree(vgeoms);
745 
746  return gout;
747 }
748 
749 GEOSGeometry*
750 LWGEOM_GEOS_makeValid(const GEOSGeometry* gin)
751 {
752  GEOSGeometry* gout;
753  char ret_char;
754 #if POSTGIS_DEBUG_LEVEL >= 3
755  LWGEOM *geos_geom;
756  char *geom_ewkt;
757 #endif
758 
759  /*
760  * Step 2: return what we got so far if already valid
761  */
762 
763  ret_char = GEOSisValid(gin);
764  if (ret_char == 2)
765  {
766  /* I don't think should ever happen */
767  lwerror("GEOSisValid(): %s", lwgeom_geos_errmsg);
768  return NULL;
769  }
770  else if (ret_char)
771  {
772 #if POSTGIS_DEBUG_LEVEL >= 3
773  geos_geom = GEOS2LWGEOM(gin, 0);
774  geom_ewkt = lwgeom_to_ewkt(geos_geom);
775  LWDEBUGF(3, "Geometry [%s] is valid. ", geom_ewkt);
776  lwgeom_free(geos_geom);
777  lwfree(geom_ewkt);
778 #endif
779 
780  /* It's valid at this step, return what we have */
781  return GEOSGeom_clone(gin);
782  }
783 
784 #if POSTGIS_DEBUG_LEVEL >= 3
785  geos_geom = GEOS2LWGEOM(gin, 0);
786  geom_ewkt = lwgeom_to_ewkt(geos_geom);
787  LWDEBUGF(3,
788  "Geometry [%s] is still not valid: %s. Will try to clean up further.",
789  geom_ewkt,
791  lwgeom_free(geos_geom);
792  lwfree(geom_ewkt);
793 #endif
794 
795  /*
796  * Step 3 : make what we got valid
797  */
798 
799  switch (GEOSGeomTypeId(gin))
800  {
801  case GEOS_MULTIPOINT:
802  case GEOS_POINT:
803  /* points are always valid, but we might have invalid ordinate values */
804  lwnotice("PUNTUAL geometry resulted invalid to GEOS -- dunno how to clean that up");
805  return NULL;
806  break;
807 
808  case GEOS_LINESTRING:
809  gout = LWGEOM_GEOS_makeValidLine(gin);
810  if (!gout) /* an exception or something */
811  {
812  /* cleanup and throw */
814  return NULL;
815  }
816  break; /* we've done */
817 
818  case GEOS_MULTILINESTRING:
819  gout = LWGEOM_GEOS_makeValidMultiLine(gin);
820  if (!gout) /* an exception or something */
821  {
822  /* cleanup and throw */
824  return NULL;
825  }
826  break; /* we've done */
827 
828  case GEOS_POLYGON:
829  case GEOS_MULTIPOLYGON:
830  {
831  gout = LWGEOM_GEOS_makeValidPolygon(gin);
832  if (!gout) /* an exception or something */
833  {
834  /* cleanup and throw */
836  return NULL;
837  }
838  break; /* we've done */
839  }
840 
841  case GEOS_GEOMETRYCOLLECTION:
842  {
843  gout = LWGEOM_GEOS_makeValidCollection(gin);
844  if (!gout) /* an exception or something */
845  {
846  /* cleanup and throw */
848  return NULL;
849  }
850  break; /* we've done */
851  }
852 
853  default:
854  {
855  char* typname = GEOSGeomType(gin);
856  lwnotice("ST_MakeValid: doesn't support geometry type: %s", typname);
857  GEOSFree(typname);
858  return NULL;
859  break;
860  }
861  }
862 
863 #if PARANOIA_LEVEL > 1
864  /*
865  * Now check if every point of input is also found in output, or abort by returning NULL
866  *
867  * Input geometry was lwgeom_in
868  */
869  {
870  int loss;
871  GEOSGeometry *pi, *po, *pd;
872 
873  /* TODO: handle some errors here...
874  * Lack of exceptions is annoying indeed,
875  * I'm getting old --strk;
876  */
877  pi = GEOSGeom_extractUniquePoints(gin);
878  po = GEOSGeom_extractUniquePoints(gout);
879  pd = GEOSDifference(pi, po); /* input points - output points */
880  GEOSGeom_destroy(pi);
881  GEOSGeom_destroy(po);
882  loss = pd && !GEOSisEmpty(pd);
883  GEOSGeom_destroy(pd);
884  if (loss)
885  {
886  lwnotice("%s [%d] Vertices lost in LWGEOM_GEOS_makeValid", __FILE__, __LINE__);
887  /* return NULL */
888  }
889  }
890 #endif /* PARANOIA_LEVEL > 1 */
891 
892  return gout;
893 }
894 #endif
895 
896 /* Exported. Uses GEOS internally */
897 LWGEOM*
899 {
900  int is3d;
901  GEOSGeom geosgeom;
902  GEOSGeometry* geosout;
903  LWGEOM* lwgeom_out;
904 
905  LWDEBUG(1, "lwgeom_make_valid enter");
906 
907  is3d = FLAGS_GET_Z(lwgeom_in->flags);
908 
909  /*
910  * Step 1 : try to convert to GEOS, if impossible, clean that up first
911  * otherwise (adding only duplicates of existing points)
912  */
913 
915 
916  lwgeom_out = lwgeom_make_geos_friendly(lwgeom_in);
917  if (!lwgeom_out) lwerror("Could not make a geos friendly geometry out of input");
918 
919  LWDEBUGF(4, "Input geom %p made GEOS-valid as %p", lwgeom_in, lwgeom_out);
920 
921  geosgeom = LWGEOM2GEOS(lwgeom_out, 1);
922  if ( lwgeom_in != lwgeom_out ) {
923  lwgeom_free(lwgeom_out);
924  }
925  if (!geosgeom)
926  {
927  lwerror("Couldn't convert POSTGIS geom to GEOS: %s", lwgeom_geos_errmsg);
928  return NULL;
929  }
930  else
931  {
932  LWDEBUG(4, "geom converted to GEOS");
933  }
934 
935 #if POSTGIS_GEOS_VERSION < 38
936  geosout = LWGEOM_GEOS_makeValid(geosgeom);
937 #else
938  geosout = GEOSMakeValid(geosgeom);
939 #endif
940  GEOSGeom_destroy(geosgeom);
941  if (!geosout) return NULL;
942 
943  lwgeom_out = GEOS2LWGEOM(geosout, is3d);
944  GEOSGeom_destroy(geosout);
945 
946  if (lwgeom_is_collection(lwgeom_in) && !lwgeom_is_collection(lwgeom_out))
947  {
948  LWGEOM** ogeoms = lwalloc(sizeof(LWGEOM*));
949  LWGEOM* ogeom;
950  LWDEBUG(3, "lwgeom_make_valid: forcing multi");
951  /* NOTE: this is safe because lwgeom_out is surely not lwgeom_in or
952  * otherwise we couldn't have a collection and a non-collection */
953  assert(lwgeom_in != lwgeom_out);
954  ogeoms[0] = lwgeom_out;
955  ogeom = (LWGEOM*)lwcollection_construct(
956  MULTITYPE[lwgeom_out->type], lwgeom_out->srid, lwgeom_out->bbox, 1, ogeoms);
957  lwgeom_out->bbox = NULL;
958  lwgeom_out = ogeom;
959  }
960 
961  lwgeom_out->srid = lwgeom_in->srid;
962  return lwgeom_out;
963 }
char lwgeom_geos_errmsg[LWGEOM_GEOS_ERRMSG_MAXSIZE]
GEOSGeometry * LWGEOM2GEOS(const LWGEOM *lwgeom, uint8_t autofix)
LWGEOM * GEOS2LWGEOM(const GEOSGeometry *geom, uint8_t want3d)
void lwgeom_geos_error(const char *fmt,...)
static POINTARRAY * ptarray_close2d(POINTARRAY *ring)
LWGEOM * lwline_make_geos_friendly(LWLINE *line)
LWGEOM * lwpoly_make_geos_friendly(LWPOLY *poly)
LWGEOM * lwgeom_make_valid(LWGEOM *lwgeom_in)
Attempts to make an invalid geometries valid w/out losing points.
LWGEOM * lwcollection_make_geos_friendly(LWCOLLECTION *g)
static LWGEOM * lwgeom_make_geos_friendly(LWGEOM *geom)
POINTARRAY * ring_make_geos_friendly(POINTARRAY *ring)
static void ptarray_strip_nan_coords_in_place(POINTARRAY *pa)
LWGEOM * lwcollection_as_lwgeom(const LWCOLLECTION *obj)
Definition: lwgeom.c:292
#define COLLECTIONTYPE
Definition: liblwgeom.h:122
#define COMPOUNDTYPE
Definition: liblwgeom.h:124
void lwgeom_free(LWGEOM *geom)
Definition: lwgeom.c:1138
#define CURVEPOLYTYPE
Definition: liblwgeom.h:125
#define MULTILINETYPE
Definition: liblwgeom.h:120
#define MULTISURFACETYPE
Definition: liblwgeom.h:127
#define LINETYPE
Definition: liblwgeom.h:117
POINTARRAY * ptarray_addPoint(const POINTARRAY *pa, uint8_t *p, size_t pdims, uint32_t where)
Add a point in a pointarray.
Definition: ptarray.c:509
#define MULTIPOINTTYPE
Definition: liblwgeom.h:119
#define POINTTYPE
LWTYPE numbers, used internally by PostGIS.
Definition: liblwgeom.h:116
char * lwgeom_to_ewkt(const LWGEOM *lwgeom)
Return an alloced string.
Definition: lwgeom.c:548
#define FLAGS_GET_Z(flags)
Definition: liblwgeom.h:179
#define MULTIPOLYGONTYPE
Definition: liblwgeom.h:121
void * lwrealloc(void *mem, size_t size)
Definition: lwutil.c:235
void lwfree(void *mem)
Definition: lwutil.c:242
#define FLAGS_NDIMS(flags)
Definition: liblwgeom.h:193
int lwgeom_is_collection(const LWGEOM *lwgeom)
Determine whether a LWGEOM can contain sub-geometries or not.
Definition: lwgeom.c:1080
#define POLYGONTYPE
Definition: liblwgeom.h:118
LWPOINT * lwpoint_construct(int32_t srid, GBOX *bbox, POINTARRAY *point)
Definition: lwpoint.c:129
#define CIRCSTRINGTYPE
Definition: liblwgeom.h:123
const char * lwtype_name(uint8_t type)
Return the type name string associated with a type number (e.g.
Definition: lwutil.c:216
void ptarray_free(POINTARRAY *pa)
Definition: ptarray.c:327
LWGEOM * lwgeom_clone(const LWGEOM *lwgeom)
Clone LWGEOM object.
Definition: lwgeom.c:474
#define MULTICURVETYPE
Definition: liblwgeom.h:126
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
int ptarray_is_closed_2d(const POINTARRAY *pa)
Definition: ptarray.c:701
This library is the generic geometry handling section of PostGIS.
uint8_t MULTITYPE[NUMTYPES]
Look-up for the correct MULTI* type promotion for singleton types.
Definition: lwgeom.c:337
#define LWDEBUG(level, msg)
Definition: lwgeom_log.h:83
#define LWDEBUGF(level, msg,...)
Definition: lwgeom_log.h:88
void lwerror(const char *fmt,...)
Write a notice out to the error handler.
Definition: lwutil.c:190
void lwnotice(const char *fmt,...)
Write a notice out to the notice handler.
Definition: lwutil.c:177
void free(void *)
static uint8_t * getPoint_internal(const POINTARRAY *pa, uint32_t n)
Definition: lwinline.h:77
uint32_t ngeoms
Definition: liblwgeom.h:594
uint32_t maxgeoms
Definition: liblwgeom.h:595
GBOX * bbox
Definition: liblwgeom.h:588
LWGEOM ** geoms
Definition: liblwgeom.h:589
uint8_t type
Definition: liblwgeom.h:476
GBOX * bbox
Definition: liblwgeom.h:472
int32_t srid
Definition: liblwgeom.h:474
lwflags_t flags
Definition: liblwgeom.h:475
POINTARRAY * points
Definition: liblwgeom.h:497
int32_t srid
Definition: liblwgeom.h:498
uint8_t type
Definition: liblwgeom.h:488
POINTARRAY ** rings
Definition: liblwgeom.h:533
uint32_t nrings
Definition: liblwgeom.h:538
double m
Definition: liblwgeom.h:428
double x
Definition: liblwgeom.h:428
double z
Definition: liblwgeom.h:428
double y
Definition: liblwgeom.h:428
lwflags_t flags
Definition: liblwgeom.h:445
uint32_t npoints
Definition: liblwgeom.h:441