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