PostGIS  3.7.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 
27 #include "liblwgeom.h"
28 #include "lwgeom_geos.h"
29 #include "liblwgeom_internal.h"
30 #include "lwgeom_log.h"
31 #include "optionlist.h"
32 
33 #include <string.h>
34 #include <stdlib.h>
35 #include <assert.h>
36 
41 
42 static void
44 {
45  uint32_t i, j = 0;
46  POINT4D *p, *np;
47  int ndims = FLAGS_NDIMS(pa->flags);
48  for ( i = 0; i < pa->npoints; i++ )
49  {
50  int isnan = 0;
51  p = (POINT4D *)(getPoint_internal(pa, i));
52  if ( isnan(p->x) || isnan(p->y) ) isnan = 1;
53  else if (ndims > 2 && isnan(p->z) ) isnan = 1;
54  else if (ndims > 3 && isnan(p->m) ) isnan = 1;
55  if ( isnan ) continue;
56 
57  np = (POINT4D *)(getPoint_internal(pa, j++));
58  if ( np != p ) {
59  np->x = p->x;
60  np->y = p->y;
61  if (ndims > 2)
62  np->z = p->z;
63  if (ndims > 3)
64  np->m = p->m;
65  }
66  }
67  pa->npoints = j;
68 }
69 
70 
71 
72 /*
73  * Ensure the geometry is "structurally" valid
74  * (enough for GEOS to accept it)
75  * May return the input untouched (if already valid).
76  * May return geometries of lower dimension (on collapses)
77  */
78 static LWGEOM*
80 {
81  LWDEBUGF(2, "lwgeom_make_geos_friendly enter (type %d)", geom->type);
82  switch (geom->type)
83  {
84  case POINTTYPE:
86  return geom;
87 
88  case LINETYPE:
89  /* lines need at least 2 points */
90  return lwline_make_geos_friendly((LWLINE*)geom);
91  break;
92 
93  case POLYGONTYPE:
94  /* polygons need all rings closed and with npoints > 3 */
95  return lwpoly_make_geos_friendly((LWPOLY*)geom);
96  break;
97 
98  case MULTILINETYPE:
99  case MULTIPOLYGONTYPE:
100  case COLLECTIONTYPE:
101  case MULTIPOINTTYPE:
103  break;
104 
105  case CIRCSTRINGTYPE:
106  case COMPOUNDTYPE:
107  case CURVEPOLYTYPE:
108  case MULTISURFACETYPE:
109  case MULTICURVETYPE:
110  default:
111  lwerror("lwgeom_make_geos_friendly: unsupported input geometry type: %s (%d)",
112  lwtype_name(geom->type),
113  geom->type);
114  break;
115  }
116  return 0;
117 }
118 
119 /*
120  * Close the point array, if not already closed in 2d.
121  * Returns the input if already closed in 2d, or a newly
122  * constructed POINTARRAY.
123  * TODO: move in ptarray.c
124  */
125 static POINTARRAY*
127 {
128  POINTARRAY* newring;
129 
130  /* close the ring if not already closed (2d only) */
131  if (!ptarray_is_closed_2d(ring))
132  {
133  /* close it up */
134  newring = ptarray_addPoint(ring, getPoint_internal(ring, 0), FLAGS_NDIMS(ring->flags), ring->npoints);
135  ring = newring;
136  }
137  return ring;
138 }
139 
140 /* May return the same input or a new one (never zero) */
141 POINTARRAY*
143 {
144  POINTARRAY* closedring;
145  POINTARRAY* ring_in = ring;
146 
148 
149  /* close the ring if not already closed (2d only) */
150  closedring = ptarray_close2d(ring);
151  if (closedring != ring) ring = closedring;
152 
153  /* return 0 for collapsed ring (after closeup) */
154 
155  while (ring->npoints < 4)
156  {
157  POINTARRAY* oring = ring;
158  LWDEBUGF(4, "ring has %d points, adding another", ring->npoints);
159  /* let's add another... */
160  ring = ptarray_addPoint(ring, getPoint_internal(ring, 0), FLAGS_NDIMS(ring->flags), ring->npoints);
161  if (oring != ring_in) ptarray_free(oring);
162  }
163 
164  return ring;
165 }
166 
167 /* Make sure all rings are closed and have > 3 points.
168  * May return the input untouched.
169  */
170 LWGEOM*
172 {
173  LWGEOM* ret;
174  POINTARRAY** new_rings;
175  uint32_t i;
176 
177  /* If the polygon has no rings there's nothing to do */
178  if (!poly->nrings) return (LWGEOM*)poly;
179 
180  /* Allocate enough pointers for all rings */
181  new_rings = lwalloc(sizeof(POINTARRAY*) * poly->nrings);
182 
183  /* All rings must be closed and have > 3 points */
184  for (i = 0; i < poly->nrings; i++)
185  {
186  POINTARRAY* ring_in = poly->rings[i];
187  POINTARRAY* ring_out = ring_make_geos_friendly(ring_in);
188 
189  if (ring_in != ring_out)
190  {
191  LWDEBUGF(
192  3, "lwpoly_make_geos_friendly: ring %d cleaned, now has %d points", i, ring_out->npoints);
193  ptarray_free(ring_in);
194  }
195  else
196  LWDEBUGF(3, "lwpoly_make_geos_friendly: ring %d untouched", i);
197 
198  assert(ring_out);
199  new_rings[i] = ring_out;
200  }
201 
202  lwfree(poly->rings);
203  poly->rings = new_rings;
204  ret = (LWGEOM*)poly;
205 
206  return ret;
207 }
208 
209 /* Need NO or >1 points. Duplicate first if only one. */
210 LWGEOM*
212 {
213  LWGEOM* ret;
214 
216 
217  if (line->points->npoints == 1) /* 0 is fine, 2 is fine */
218  {
219 #if 1
220  /* Duplicate point */
221  line->points = ptarray_addPoint(line->points,
222  getPoint_internal(line->points, 0),
223  FLAGS_NDIMS(line->points->flags),
224  line->points->npoints);
225  ret = (LWGEOM*)line;
226 #else
227  /* Turn into a point */
228  ret = (LWGEOM*)lwpoint_construct(line->srid, 0, line->points);
229 #endif
230  return ret;
231  }
232  else
233  {
234  return (LWGEOM*)line;
235  /* return lwline_clone(line); */
236  }
237 }
238 
239 LWGEOM*
241 {
242  LWGEOM** new_geoms;
243  uint32_t i, new_ngeoms = 0;
244  LWCOLLECTION* ret;
245 
246  if ( ! g->ngeoms ) {
247  LWDEBUG(3, "lwcollection_make_geos_friendly: returning input untouched");
248  return lwcollection_as_lwgeom(g);
249  }
250 
251  /* enough space for all components */
252  new_geoms = lwalloc(sizeof(LWGEOM*) * g->ngeoms);
253 
254  ret = lwalloc(sizeof(LWCOLLECTION));
255  memcpy(ret, g, sizeof(LWCOLLECTION));
256  ret->maxgeoms = g->ngeoms;
257 
258  for (i = 0; i < g->ngeoms; i++)
259  {
260  LWGEOM* newg = lwgeom_make_geos_friendly(g->geoms[i]);
261  if (!newg) continue;
262  if ( newg != g->geoms[i] ) {
263  new_geoms[new_ngeoms++] = newg;
264  } else {
265  new_geoms[new_ngeoms++] = lwgeom_clone(newg);
266  }
267  }
268 
269  ret->bbox = NULL; /* recompute later... */
270 
271  ret->ngeoms = new_ngeoms;
272  if (new_ngeoms)
273  ret->geoms = new_geoms;
274  else
275  {
276  free(new_geoms);
277  ret->geoms = NULL;
278  ret->maxgeoms = 0;
279  }
280 
281  return (LWGEOM*)ret;
282 }
283 
284 /* Exported. Uses GEOS internally */
285 LWGEOM*
287 {
288  return lwgeom_make_valid_params(lwgeom_in, NULL);
289 }
290 
291 /* Exported. Uses GEOS internally */
292 LWGEOM*
293 lwgeom_make_valid_params(LWGEOM* lwgeom_in, char* make_valid_params)
294 {
295  int is3d;
296  GEOSGeom geosgeom;
297  GEOSGeometry* geosout;
298  LWGEOM* lwgeom_out;
299 
300  LWDEBUG(1, "lwgeom_make_valid enter");
301 
302  is3d = FLAGS_GET_Z(lwgeom_in->flags);
303 
304  /*
305  * Step 1 : try to convert to GEOS, if impossible, clean that up first
306  * otherwise (adding only duplicates of existing points)
307  */
308 
310 
311  lwgeom_out = lwgeom_make_geos_friendly(lwgeom_in);
312  if (!lwgeom_out) lwerror("Could not make a geos friendly geometry out of input");
313 
314  LWDEBUGF(4, "Input geom %p made GEOS-valid as %p", lwgeom_in, lwgeom_out);
315 
316  geosgeom = LWGEOM2GEOS(lwgeom_out, 1);
317  if ( lwgeom_in != lwgeom_out ) {
318  lwgeom_free(lwgeom_out);
319  }
320  if (!geosgeom)
321  {
322  lwerror("Couldn't convert POSTGIS geom to GEOS: %s", lwgeom_geos_errmsg);
323  return NULL;
324  }
325  else
326  {
327  LWDEBUG(4, "geom converted to GEOS");
328  }
329 
330 #if POSTGIS_GEOS_VERSION < 31000
331  geosout = GEOSMakeValid(geosgeom);
332 #else
333  if (!make_valid_params) {
334  geosout = GEOSMakeValid(geosgeom);
335  }
336  else {
337  /*
338  * Set up a parameters object for this
339  * make valid operation before calling
340  * it
341  */
342  const char *value;
343  char *param_list[OPTION_LIST_SIZE];
344  char param_list_text[OPTION_LIST_SIZE];
345  strncpy(param_list_text, make_valid_params, OPTION_LIST_SIZE-1);
346  param_list_text[OPTION_LIST_SIZE-1] = '\0'; /* ensure null-termination */
347  memset(param_list, 0, sizeof(param_list));
348  option_list_parse(param_list_text, param_list);
349  GEOSMakeValidParams *params = GEOSMakeValidParams_create();
350  value = option_list_search(param_list, "method");
351  if (value) {
352  if (strcasecmp(value, "linework") == 0) {
353  GEOSMakeValidParams_setMethod(params, GEOS_MAKE_VALID_LINEWORK);
354  }
355  else if (strcasecmp(value, "structure") == 0) {
356  GEOSMakeValidParams_setMethod(params, GEOS_MAKE_VALID_STRUCTURE);
357  }
358  else
359  {
360  GEOSMakeValidParams_destroy(params);
361  lwerror("Unsupported value for 'method', '%s'. Use 'linework' or 'structure'.", value);
362  }
363  }
364  value = option_list_search(param_list, "keepcollapsed");
365  if (value) {
366  if (strcasecmp(value, "true") == 0) {
367  GEOSMakeValidParams_setKeepCollapsed(params, 1);
368  }
369  else if (strcasecmp(value, "false") == 0) {
370  GEOSMakeValidParams_setKeepCollapsed(params, 0);
371  }
372  else
373  {
374  GEOSMakeValidParams_destroy(params);
375  lwerror("Unsupported value for 'keepcollapsed', '%s'. Use 'true' or 'false'", value);
376  }
377  }
378  geosout = GEOSMakeValidWithParams(geosgeom, params);
379  GEOSMakeValidParams_destroy(params);
380  }
381 #endif
382  GEOSGeom_destroy(geosgeom);
383  if (!geosout) return NULL;
384 
385  lwgeom_out = GEOS2LWGEOM(geosout, is3d);
386  GEOSGeom_destroy(geosout);
387 
388  if (lwgeom_is_collection(lwgeom_in) && !lwgeom_is_collection(lwgeom_out))
389  {
390  LWGEOM** ogeoms = lwalloc(sizeof(LWGEOM*));
391  LWGEOM* ogeom;
392  LWDEBUG(3, "lwgeom_make_valid: forcing multi");
393  /* NOTE: this is safe because lwgeom_out is surely not lwgeom_in or
394  * otherwise we couldn't have a collection and a non-collection */
395  assert(lwgeom_in != lwgeom_out);
396  ogeoms[0] = lwgeom_out;
397  ogeom = (LWGEOM*)lwcollection_construct(
398  MULTITYPE[lwgeom_out->type], lwgeom_out->srid, lwgeom_out->bbox, 1, ogeoms);
399  lwgeom_out->bbox = NULL;
400  lwgeom_out = ogeom;
401  }
402 
403  lwgeom_out->srid = lwgeom_in->srid;
404  return lwgeom_out;
405 }
char lwgeom_geos_errmsg[LWGEOM_GEOS_ERRMSG_MAXSIZE]
GEOSGeometry * LWGEOM2GEOS(const LWGEOM *lwgeom, uint8_t autofix)
void lwgeom_geos_error(const char *fmt,...)
void(*) LWGEOM GEOS2LWGEOM)(const GEOSGeometry *geom, uint8_t want3d)
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:1218
#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:530
#define MULTIPOINTTYPE
Definition: liblwgeom.h:105
#define POINTTYPE
LWTYPE numbers, used internally by PostGIS.
Definition: liblwgeom.h:102
#define FLAGS_GET_Z(flags)
Definition: liblwgeom.h:165
#define MULTIPOLYGONTYPE
Definition: liblwgeom.h:107
void lwfree(void *mem)
Definition: lwutil.c:248
#define FLAGS_NDIMS(flags)
Definition: liblwgeom.h:179
int lwgeom_is_collection(const LWGEOM *lwgeom)
Determine whether a LWGEOM contains sub-geometries or not This basically just checks that the struct ...
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:327
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:710
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:101
#define LWDEBUGF(level, msg,...)
Definition: lwgeom_log.h:106
void void lwerror(const char *fmt,...) __attribute__((format(printf
Write a notice out to the error handler.
void free(void *)
static uint8_t * getPoint_internal(const POINTARRAY *pa, uint32_t n)
Definition: lwinline.h:75
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