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