PostGIS 3.0.6dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches
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 */
43GEOSGeometry* LWGEOM_GEOS_getPointN(const GEOSGeometry*, uint32_t);
44GEOSGeometry*
45LWGEOM_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 */
125static 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 */
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) */
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 */
217LWGEOM*
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. */
257LWGEOM*
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
284LWGEOM*
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 {
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 */
324static GEOSGeometry*
325LWGEOM_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 */
343static GEOSGeometry*
344LWGEOM_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
548static GEOSGeometry*
549LWGEOM_GEOS_makeValidLine(const GEOSGeometry* gin)
550{
551 GEOSGeometry* noded;
552 noded = LWGEOM_GEOS_nodeLines(gin);
553 return noded;
554}
555
556static GEOSGeometry*
557LWGEOM_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 */
653static GEOSGeometry*
654LWGEOM_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
705GEOSGeometry*
706LWGEOM_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 */
853LWGEOM*
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;
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,...)
LWGEOM * lwline_make_geos_friendly(LWLINE *line)
POINTARRAY * ptarray_close2d(POINTARRAY *ring)
LWGEOM * lwpoly_make_geos_friendly(LWPOLY *poly)
LWGEOM * lwcollection_make_geos_friendly(LWCOLLECTION *g)
LWGEOM * lwgeom_make_valid(LWGEOM *lwgeom_in)
Attempts to make an invalid geometries valid w/out losing points.
POINTARRAY * ring_make_geos_friendly(POINTARRAY *ring)
static LWGEOM * lwgeom_make_geos_friendly(LWGEOM *geom)
const char * lwtype_name(uint8_t type)
Return the type name string associated with a type number (e.g.
Definition lwutil.c:216
LWCOLLECTION * lwcollection_construct(uint8_t type, int32_t srid, GBOX *bbox, uint32_t ngeoms, LWGEOM **geoms)
#define COLLECTIONTYPE
Definition liblwgeom.h:122
#define COMPOUNDTYPE
Definition liblwgeom.h:124
void * lwrealloc(void *mem, size_t size)
Definition lwutil.c:235
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
LWPOINT * lwpoint_construct(int32_t srid, GBOX *bbox, POINTARRAY *point)
Definition lwpoint.c:129
#define MULTIPOINTTYPE
Definition liblwgeom.h:119
char * lwgeom_to_ewkt(const LWGEOM *lwgeom)
Return an alloced string.
Definition lwgeom.c:547
#define POINTTYPE
LWTYPE numbers, used internally by PostGIS.
Definition liblwgeom.h:116
#define FLAGS_GET_Z(flags)
Definition liblwgeom.h:179
void * lwalloc(size_t size)
Definition lwutil.c:227
#define MULTIPOLYGONTYPE
Definition liblwgeom.h:121
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:1079
#define POLYGONTYPE
Definition liblwgeom.h:118
#define CIRCSTRINGTYPE
Definition liblwgeom.h:123
void ptarray_free(POINTARRAY *pa)
Definition ptarray.c:327
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 MULTICURVETYPE
Definition liblwgeom.h:126
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:336
#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:67
uint32_t ngeoms
Definition liblwgeom.h:566
uint32_t maxgeoms
Definition liblwgeom.h:567
GBOX * bbox
Definition liblwgeom.h:560
LWGEOM ** geoms
Definition liblwgeom.h:561
uint8_t type
Definition liblwgeom.h:448
GBOX * bbox
Definition liblwgeom.h:444
int32_t srid
Definition liblwgeom.h:446
lwflags_t flags
Definition liblwgeom.h:447
POINTARRAY * points
Definition liblwgeom.h:469
uint8_t type
Definition liblwgeom.h:472
int32_t srid
Definition liblwgeom.h:470
POINTARRAY ** rings
Definition liblwgeom.h:505
uint32_t nrings
Definition liblwgeom.h:510
lwflags_t flags
Definition liblwgeom.h:417
uint32_t npoints
Definition liblwgeom.h:413