PostGIS  3.3.9dev-r@@SVN_REVISION@@
liblwgeom/lwgeom_geos.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 2011-2020 Sandro Santilli <strk@kbt.io>
22  * Copyright 2015-2018 Daniel Baston <dbaston@gmail.com>
23  * Copyright 2017-2018 Darafei Praliaskouski <me@komzpa.net>
24  *
25  **********************************************************************/
26 
27 #include "lwgeom_geos.h"
28 #include "liblwgeom.h"
29 #include "liblwgeom_internal.h"
30 #include "lwgeom_log.h"
31 #include "lwrandom.h"
32 
33 #include <stdarg.h>
34 #include <stdlib.h>
35 
36 LWTIN* lwtin_from_geos(const GEOSGeometry* geom, uint8_t want3d);
37 
38 #define AUTOFIX LW_TRUE
39 #define LWGEOM_GEOS_ERRMSG_MAXSIZE 256
41 
42 extern void
43 lwgeom_geos_error(const char* fmt, ...)
44 {
45  va_list ap;
46  va_start(ap, fmt);
47 
48  /* Call the supplied function */
51 
52  va_end(ap);
53 }
54 
55 /* Destroy any non-null GEOSGeometry* pointers passed as arguments */
56 #define GEOS_FREE(...) \
57 do { \
58  geos_destroy((sizeof((void*[]){__VA_ARGS__})/sizeof(void*)), __VA_ARGS__); \
59 } while (0)
60 
61 /* Pass the latest GEOS error to lwerror, then return NULL */
62 #define GEOS_FAIL() \
63 do { \
64  lwerror("%s: GEOS Error: %s", __func__, lwgeom_geos_errmsg); \
65  return NULL; \
66 } while (0)
67 
68 /* Pass the latest GEOS error to lwdebug, then return NULL */
69 #define GEOS_FAIL_DEBUG() \
70  do \
71  { \
72  lwdebug(1, "%s: GEOS Error: %s", __func__, lwgeom_geos_errmsg); \
73  return NULL; \
74  } while (0)
75 
76 #define GEOS_FREE_AND_FAIL(...) \
77 do { \
78  GEOS_FREE(__VA_ARGS__); \
79  GEOS_FAIL(); \
80 } while (0)
81 
82 #define GEOS_FREE_AND_FAIL_DEBUG(...) \
83  do \
84  { \
85  GEOS_FREE(__VA_ARGS__); \
86  GEOS_FAIL_DEBUG(); \
87  } while (0)
88 
89 /* Return the consistent SRID of all inputs, or call lwerror
90  * in case of SRID mismatch. */
91 #define RESULT_SRID(...) \
92  (get_result_srid((sizeof((const void*[]){__VA_ARGS__})/sizeof(void*)), __func__, __VA_ARGS__))
93 
94 /* Free any non-null GEOSGeometry* pointers passed as arguments *
95  * Called by GEOS_FREE, which populates 'count' */
96 static void geos_destroy(size_t count, ...) {
97  va_list ap;
98  va_start(ap, count);
99  while (count--)
100  {
101  GEOSGeometry* g = va_arg(ap, GEOSGeometry*);
102  if (g)
103  {
104  GEOSGeom_destroy(g);
105  }
106  }
107  va_end(ap);
108 }
109 
110 /*
111 ** GEOS <==> PostGIS conversion functions
112 **
113 ** Default conversion creates a GEOS point array, then iterates through the
114 ** PostGIS points, setting each value in the GEOS array one at a time.
115 **
116 */
117 
118 /* Return a POINTARRAY from a GEOSCoordSeq */
119 POINTARRAY*
120 ptarray_from_GEOSCoordSeq(const GEOSCoordSequence* cs, uint8_t want3d)
121 {
122  uint32_t dims = 2;
123  POINTARRAY* pa;
124  uint32_t size = 0;
125 #if POSTGIS_GEOS_VERSION < 31000
126  uint32_t i;
127  POINT4D point = { 0.0, 0.0, 0.0, 0.0 };
128 #endif
129 
130  LWDEBUG(2, "ptarray_fromGEOSCoordSeq called");
131 
132  if (!GEOSCoordSeq_getSize(cs, &size)) lwerror("Exception thrown");
133 
134  LWDEBUGF(4, " GEOSCoordSeq size: %d", size);
135 
136  if (want3d)
137  {
138  if (!GEOSCoordSeq_getDimensions(cs, &dims)) lwerror("Exception thrown");
139 
140  LWDEBUGF(4, " GEOSCoordSeq dimensions: %d", dims);
141 
142  /* forget higher dimensions (if any) */
143  if (dims > 3) dims = 3;
144  }
145 
146  LWDEBUGF(4, " output dimensions: %d", dims);
147 
148  pa = ptarray_construct((dims == 3), 0, size);
149 #if POSTGIS_GEOS_VERSION >= 31000
150  GEOSCoordSeq_copyToBuffer(cs, (double*) pa->serialized_pointlist, (dims == 3), 0);
151  return pa;
152 #else
153  for (i = 0; i < size; i++)
154  {
155 #if POSTGIS_GEOS_VERSION < 30800
156  GEOSCoordSeq_getX(cs, i, &(point.x));
157  GEOSCoordSeq_getY(cs, i, &(point.y));
158  if (dims >= 3) GEOSCoordSeq_getZ(cs, i, &(point.z));
159 #else
160  if (dims >= 3)
161  GEOSCoordSeq_getXYZ(cs, i, &(point.x), &(point.y), &(point.z));
162  else
163  GEOSCoordSeq_getXY(cs, i, &(point.x), &(point.y));
164 #endif
165  ptarray_set_point4d(pa, i, &point);
166  }
167 
168  return pa;
169 #endif
170 }
171 
172 /* Return an LWGEOM from a Geometry */
173 LWGEOM*
174 GEOS2LWGEOM(const GEOSGeometry* geom, uint8_t want3d)
175 {
176  int type = GEOSGeomTypeId(geom);
177  int SRID = GEOSGetSRID(geom);
178 
179  /* GEOS's 0 is equivalent to our unknown as for SRID values */
180  if (SRID == 0) SRID = SRID_UNKNOWN;
181 
182  if (want3d && !GEOSHasZ(geom))
183  {
184  LWDEBUG(3, "Geometry has no Z, won't provide one");
185  want3d = 0;
186  }
187 
188  switch (type)
189  {
190  const GEOSCoordSequence* cs;
191  POINTARRAY *pa, **ppaa;
192  const GEOSGeometry* g;
193  LWGEOM** geoms;
194  uint32_t i, ngeoms;
195 
196  case GEOS_POINT:
197  LWDEBUG(4, "lwgeom_from_geometry: it's a Point");
198  cs = GEOSGeom_getCoordSeq(geom);
199  if (GEOSisEmpty(geom)) return (LWGEOM*)lwpoint_construct_empty(SRID, want3d, 0);
200  pa = ptarray_from_GEOSCoordSeq(cs, want3d);
201  return (LWGEOM*)lwpoint_construct(SRID, NULL, pa);
202 
203  case GEOS_LINESTRING:
204  case GEOS_LINEARRING:
205  LWDEBUG(4, "lwgeom_from_geometry: it's a LineString or LinearRing");
206  if (GEOSisEmpty(geom)) return (LWGEOM*)lwline_construct_empty(SRID, want3d, 0);
207 
208  cs = GEOSGeom_getCoordSeq(geom);
209  pa = ptarray_from_GEOSCoordSeq(cs, want3d);
210  return (LWGEOM*)lwline_construct(SRID, NULL, pa);
211 
212  case GEOS_POLYGON:
213  LWDEBUG(4, "lwgeom_from_geometry: it's a Polygon");
214  if (GEOSisEmpty(geom)) return (LWGEOM*)lwpoly_construct_empty(SRID, want3d, 0);
215  ngeoms = GEOSGetNumInteriorRings(geom);
216  ppaa = lwalloc(sizeof(POINTARRAY*) * (ngeoms + 1));
217  g = GEOSGetExteriorRing(geom);
218  cs = GEOSGeom_getCoordSeq(g);
219  ppaa[0] = ptarray_from_GEOSCoordSeq(cs, want3d);
220  for (i = 0; i < ngeoms; i++)
221  {
222  g = GEOSGetInteriorRingN(geom, i);
223  cs = GEOSGeom_getCoordSeq(g);
224  ppaa[i + 1] = ptarray_from_GEOSCoordSeq(cs, want3d);
225  }
226  return (LWGEOM*)lwpoly_construct(SRID, NULL, ngeoms + 1, ppaa);
227 
228  case GEOS_MULTIPOINT:
229  case GEOS_MULTILINESTRING:
230  case GEOS_MULTIPOLYGON:
231  case GEOS_GEOMETRYCOLLECTION:
232  LWDEBUG(4, "lwgeom_from_geometry: it's a Collection or Multi");
233 
234  ngeoms = GEOSGetNumGeometries(geom);
235  geoms = NULL;
236  if (ngeoms)
237  {
238  geoms = lwalloc(sizeof(LWGEOM*) * ngeoms);
239  for (i = 0; i < ngeoms; i++)
240  {
241  g = GEOSGetGeometryN(geom, i);
242  geoms[i] = GEOS2LWGEOM(g, want3d);
243  }
244  }
245  return (LWGEOM*)lwcollection_construct(type, SRID, NULL, ngeoms, geoms);
246 
247  default:
248  lwerror("GEOS2LWGEOM: unknown geometry type: %d", type);
249  return NULL;
250  }
251 }
252 
253 GEOSCoordSeq ptarray_to_GEOSCoordSeq(const POINTARRAY*, uint8_t fix_ring);
254 
255 GEOSCoordSeq
256 ptarray_to_GEOSCoordSeq(const POINTARRAY* pa, uint8_t fix_ring)
257 {
258  uint32_t dims = 2;
259  uint32_t i;
260  int append_points = 0;
261  const POINT3D *p3d = NULL;
262  const POINT2D* p2d = NULL;
263  GEOSCoordSeq sq;
264 
265  if (FLAGS_GET_Z(pa->flags)) dims = 3;
266 
267  if (fix_ring)
268  {
269  if (pa->npoints < 1)
270  {
271  lwerror("ptarray_to_GEOSCoordSeq called with fix_ring and 0 vertices in ring, cannot fix");
272  return NULL;
273  }
274  else
275  {
276  if (pa->npoints < 4) append_points = 4 - pa->npoints;
277  if (!ptarray_is_closed_2d(pa) && append_points == 0) append_points = 1;
278  }
279  }
280 
281 #if POSTGIS_GEOS_VERSION >= 31000
282  if (append_points == 0) {
283  sq = GEOSCoordSeq_copyFromBuffer((const double*) pa->serialized_pointlist, pa->npoints, FLAGS_GET_Z(pa->flags), FLAGS_GET_M(pa->flags));
284  if (!sq)
285  {
286  lwerror("Error creating GEOS Coordinate Sequence");
287  return NULL;
288  }
289  return sq;
290  }
291 #endif
292  if (!(sq = GEOSCoordSeq_create(pa->npoints + append_points, dims)))
293  {
294  lwerror("Error creating GEOS Coordinate Sequence");
295  return NULL;
296  }
297 
298  for (i = 0; i < pa->npoints; i++)
299  {
300  if (dims == 3)
301  {
302  p3d = getPoint3d_cp(pa, i);
303  p2d = (const POINT2D*)p3d;
304  LWDEBUGF(4, "Point: %g,%g,%g", p3d->x, p3d->y, p3d->z);
305  }
306  else
307  {
308  p2d = getPoint2d_cp(pa, i);
309  LWDEBUGF(4, "Point: %g,%g", p2d->x, p2d->y);
310  }
311 
312 #if POSTGIS_GEOS_VERSION < 30800
313  GEOSCoordSeq_setX(sq, i, p2d->x);
314  GEOSCoordSeq_setY(sq, i, p2d->y);
315  if (dims == 3) GEOSCoordSeq_setZ(sq, i, p3d->z);
316 #else
317  if (dims == 3)
318  GEOSCoordSeq_setXYZ(sq, i, p2d->x, p2d->y, p3d->z);
319  else
320  GEOSCoordSeq_setXY(sq, i, p2d->x, p2d->y);
321 #endif
322 
323  }
324 
325  if (append_points)
326  {
327  if (dims == 3)
328  {
329  p3d = getPoint3d_cp(pa, 0);
330  p2d = (const POINT2D*)p3d;
331  }
332  else
333  p2d = getPoint2d_cp(pa, 0);
334  for (i = pa->npoints; i < pa->npoints + append_points; i++)
335  {
336 #if POSTGIS_GEOS_VERSION < 30800
337  GEOSCoordSeq_setX(sq, i, p2d->x);
338  GEOSCoordSeq_setY(sq, i, p2d->y);
339 #else
340  GEOSCoordSeq_setXY(sq, i, p2d->x, p2d->y);
341 #endif
342 
343  if (dims == 3) GEOSCoordSeq_setZ(sq, i, p3d->z);
344  }
345  }
346 
347  return sq;
348 
349 }
350 
351 static inline GEOSGeometry*
352 ptarray_to_GEOSLinearRing(const POINTARRAY* pa, uint8_t autofix)
353 {
354  GEOSCoordSeq sq;
355  GEOSGeom g;
356  sq = ptarray_to_GEOSCoordSeq(pa, autofix);
357  g = GEOSGeom_createLinearRing(sq);
358  return g;
359 }
360 
361 GEOSGeometry*
362 GBOX2GEOS(const GBOX* box)
363 {
364  GEOSGeometry* envelope;
365  GEOSGeometry* ring;
366  GEOSCoordSequence* seq = GEOSCoordSeq_create(5, 2);
367  if (!seq) return NULL;
368 
369 #if POSTGIS_GEOS_VERSION < 30800
370  GEOSCoordSeq_setX(seq, 0, box->xmin);
371  GEOSCoordSeq_setY(seq, 0, box->ymin);
372 
373  GEOSCoordSeq_setX(seq, 1, box->xmax);
374  GEOSCoordSeq_setY(seq, 1, box->ymin);
375 
376  GEOSCoordSeq_setX(seq, 2, box->xmax);
377  GEOSCoordSeq_setY(seq, 2, box->ymax);
378 
379  GEOSCoordSeq_setX(seq, 3, box->xmin);
380  GEOSCoordSeq_setY(seq, 3, box->ymax);
381 
382  GEOSCoordSeq_setX(seq, 4, box->xmin);
383  GEOSCoordSeq_setY(seq, 4, box->ymin);
384 #else
385  GEOSCoordSeq_setXY(seq, 0, box->xmin, box->ymin);
386  GEOSCoordSeq_setXY(seq, 1, box->xmax, box->ymin);
387  GEOSCoordSeq_setXY(seq, 2, box->xmax, box->ymax);
388  GEOSCoordSeq_setXY(seq, 3, box->xmin, box->ymax);
389  GEOSCoordSeq_setXY(seq, 4, box->xmin, box->ymin);
390 #endif
391 
392  ring = GEOSGeom_createLinearRing(seq);
393  if (!ring)
394  {
395  GEOSCoordSeq_destroy(seq);
396  return NULL;
397  }
398 
399  envelope = GEOSGeom_createPolygon(ring, NULL, 0);
400  if (!envelope)
401  {
402  GEOSGeom_destroy(ring);
403  return NULL;
404  }
405 
406  return envelope;
407 }
408 
409 GEOSGeometry*
410 LWGEOM2GEOS(const LWGEOM* lwgeom, uint8_t autofix)
411 {
412  GEOSCoordSeq sq;
413  GEOSGeom g, shell;
414  GEOSGeom* geoms = NULL;
415  uint32_t ngeoms, i, j;
416  int geostype;
417 #if LWDEBUG_LEVEL >= 4
418  char* wkt;
419 #endif
420 
421  if (autofix)
422  {
423  /* cross fingers and try without autofix, maybe it'll work? */
424  g = LWGEOM2GEOS(lwgeom, LW_FALSE);
425  if (g) return g;
426  }
427 
428  LWDEBUGF(4, "LWGEOM2GEOS got a %s", lwtype_name(lwgeom->type));
429 
430  if (lwgeom_type_arc(lwgeom))
431  {
432  LWGEOM* lwgeom_stroked = lwgeom_stroke(lwgeom, 32);
433  GEOSGeometry* g = LWGEOM2GEOS(lwgeom_stroked, autofix);
434  lwgeom_free(lwgeom_stroked);
435  return g;
436  }
437 
438  LWPOINT* lwp = NULL;
439  LWPOLY* lwpoly = NULL;
440  LWLINE* lwl = NULL;
441  LWCOLLECTION* lwc = NULL;
442 
443  switch (lwgeom->type)
444  {
445  case POINTTYPE:
446  lwp = (LWPOINT*)lwgeom;
447 
448  if (lwgeom_is_empty(lwgeom))
449  g = GEOSGeom_createEmptyPoint();
450  else
451  {
452 #if POSTGIS_GEOS_VERSION < 30800
453  sq = ptarray_to_GEOSCoordSeq(lwp->point, 0);
454  g = GEOSGeom_createPoint(sq);
455 #else
456  if (lwgeom_has_z(lwgeom))
457  {
458  sq = ptarray_to_GEOSCoordSeq(lwp->point, 0);
459  g = GEOSGeom_createPoint(sq);
460  }
461  else
462  {
463  const POINT2D* p = getPoint2d_cp(lwp->point, 0);
464  g = GEOSGeom_createPointFromXY(p->x, p->y);
465  }
466 #endif
467  }
468  if (!g) return NULL;
469  break;
470 
471  case LINETYPE:
472  lwl = (LWLINE*)lwgeom;
473  /* TODO: if (autofix) */
474  if (lwl->points->npoints == 1)
475  {
476  /* Duplicate point, to make geos-friendly */
477  lwl->points = ptarray_addPoint(lwl->points,
478  getPoint_internal(lwl->points, 0),
479  FLAGS_NDIMS(lwl->points->flags),
480  lwl->points->npoints);
481  }
482  sq = ptarray_to_GEOSCoordSeq(lwl->points, 0);
483  g = GEOSGeom_createLineString(sq);
484  if (!g) return NULL;
485  break;
486 
487  case POLYGONTYPE:
488  lwpoly = (LWPOLY*)lwgeom;
489  if (lwgeom_is_empty(lwgeom))
490  g = GEOSGeom_createEmptyPolygon();
491  else
492  {
493  shell = ptarray_to_GEOSLinearRing(lwpoly->rings[0], autofix);
494  if (!shell) return NULL;
495  ngeoms = lwpoly->nrings - 1;
496  if (ngeoms > 0) geoms = lwalloc(sizeof(GEOSGeom) * ngeoms);
497 
498  for (i = 1; i < lwpoly->nrings; i++)
499  {
500  geoms[i - 1] = ptarray_to_GEOSLinearRing(lwpoly->rings[i], autofix);
501  if (!geoms[i - 1])
502  {
503  uint32_t k;
504  for (k = 0; k < i - 1; k++)
505  GEOSGeom_destroy(geoms[k]);
506  lwfree(geoms);
507  GEOSGeom_destroy(shell);
508  return NULL;
509  }
510  }
511  g = GEOSGeom_createPolygon(shell, geoms, ngeoms);
512  if (geoms) lwfree(geoms);
513  }
514  if (!g) return NULL;
515  break;
516 
517  case TRIANGLETYPE:
518  if (lwgeom_is_empty(lwgeom))
519  g = GEOSGeom_createEmptyPolygon();
520  else
521  {
522  LWTRIANGLE *lwt = (LWTRIANGLE *)lwgeom;
523  shell = ptarray_to_GEOSLinearRing(lwt->points, autofix);
524  if (!shell)
525  return NULL;
526  g = GEOSGeom_createPolygon(shell, NULL, 0);
527  }
528  if (!g)
529  return NULL;
530  break;
531  case MULTIPOINTTYPE:
532  case MULTILINETYPE:
533  case MULTIPOLYGONTYPE:
534  case TINTYPE:
535  case COLLECTIONTYPE:
536  if (lwgeom->type == MULTIPOINTTYPE)
537  geostype = GEOS_MULTIPOINT;
538  else if (lwgeom->type == MULTILINETYPE)
539  geostype = GEOS_MULTILINESTRING;
540  else if (lwgeom->type == MULTIPOLYGONTYPE)
541  geostype = GEOS_MULTIPOLYGON;
542  else
543  geostype = GEOS_GEOMETRYCOLLECTION;
544 
545  lwc = (LWCOLLECTION*)lwgeom;
546 
547  ngeoms = lwc->ngeoms;
548  if (ngeoms > 0) geoms = lwalloc(sizeof(GEOSGeom) * ngeoms);
549 
550  j = 0;
551  for (i = 0; i < ngeoms; ++i)
552  {
553  GEOSGeometry* g;
554 
555  /* if (lwgeom_is_empty(lwc->geoms[i])) continue; */
556 
557  g = LWGEOM2GEOS(lwc->geoms[i], 0);
558  if (!g)
559  {
560  uint32_t k;
561  for (k = 0; k < j; k++)
562  GEOSGeom_destroy(geoms[k]);
563  lwfree(geoms);
564  return NULL;
565  }
566  geoms[j++] = g;
567  }
568  g = GEOSGeom_createCollection(geostype, geoms, j);
569  if (ngeoms > 0) lwfree(geoms);
570  if (!g) return NULL;
571  break;
572 
573  default:
574  lwerror("Unknown geometry type: %d - %s", lwgeom->type, lwtype_name(lwgeom->type));
575  return NULL;
576  }
577 
578  GEOSSetSRID(g, lwgeom->srid);
579 
580 #if LWDEBUG_LEVEL >= 4
581  wkt = GEOSGeomToWKT(g);
582  LWDEBUGF(4, "LWGEOM2GEOS: GEOSGeom: %s", wkt);
583  free(wkt);
584 #endif
585 
586  return g;
587 }
588 
589 GEOSGeometry*
590 make_geos_point(double x, double y)
591 {
592  GEOSCoordSequence* seq = GEOSCoordSeq_create(1, 2);
593  GEOSGeometry* geom = NULL;
594 
595  if (!seq) return NULL;
596 
597 #if POSTGIS_GEOS_VERSION < 30800
598  GEOSCoordSeq_setX(seq, 0, x);
599  GEOSCoordSeq_setY(seq, 0, y);
600 #else
601  GEOSCoordSeq_setXY(seq, 0, x, y);
602 #endif
603 
604  geom = GEOSGeom_createPoint(seq);
605  if (!geom) GEOSCoordSeq_destroy(seq);
606  return geom;
607 }
608 
609 GEOSGeometry*
610 make_geos_segment(double x1, double y1, double x2, double y2)
611 {
612  GEOSCoordSequence* seq = GEOSCoordSeq_create(2, 2);
613  GEOSGeometry* geom = NULL;
614 
615  if (!seq) return NULL;
616 
617 #if POSTGIS_GEOS_VERSION < 30800
618  GEOSCoordSeq_setX(seq, 0, x1);
619  GEOSCoordSeq_setY(seq, 0, y1);
620  GEOSCoordSeq_setX(seq, 1, x2);
621  GEOSCoordSeq_setY(seq, 1, y2);
622 #else
623  GEOSCoordSeq_setXY(seq, 0, x1, y1);
624  GEOSCoordSeq_setXY(seq, 1, x2, y2);
625 #endif
626 
627  geom = GEOSGeom_createLineString(seq);
628  if (!geom) GEOSCoordSeq_destroy(seq);
629  return geom;
630 }
631 
632 const char*
634 {
635  const char* ver = GEOSversion();
636  return ver;
637 }
638 
639 /* Return the consistent SRID of all input.
640  * Intended to be called from RESULT_SRID macro */
641 static int32_t
642 get_result_srid(size_t count, const char* funcname, ...)
643 {
644  va_list ap;
645  va_start(ap, funcname);
646  int32_t srid = SRID_INVALID;
647  size_t i;
648  for(i = 0; i < count; i++)
649  {
650  LWGEOM* g = va_arg(ap, LWGEOM*);
651  if (!g)
652  {
653  lwerror("%s: Geometry is null", funcname);
654  va_end(ap);
655  return SRID_INVALID;
656  }
657  if (i == 0)
658  {
659  srid = g->srid;
660  }
661  else
662  {
663  if (g->srid != srid)
664  {
665  lwerror("%s: Operation on mixed SRID geometries (%d != %d)", funcname, srid, g->srid);
666  va_end(ap);
667  return SRID_INVALID;
668  }
669  }
670  }
671  va_end(ap);
672  return srid;
673 }
674 
675 LWGEOM*
677 {
678  LWGEOM* result;
679  int32_t srid = RESULT_SRID(geom);
680  uint8_t is3d = FLAGS_GET_Z(geom->flags);
681  GEOSGeometry* g;
682 
683  if (srid == SRID_INVALID) return NULL;
684 
685  initGEOS(lwnotice, lwgeom_geos_error);
686 
687  if (!(g = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL();
688 
689  if (GEOSNormalize(g) == -1) GEOS_FREE_AND_FAIL(g);
690  GEOSSetSRID(g, srid);
691 
692  if (!(result = GEOS2LWGEOM(g, is3d))) GEOS_FREE_AND_FAIL(g);
693 
694  GEOSGeom_destroy(g);
695  return result;
696 }
697 
698 LWGEOM*
699 lwgeom_intersection(const LWGEOM* g1, const LWGEOM* g2)
700 {
701  return lwgeom_intersection_prec(g1, g2, -1.0);
702 }
703 
704 LWGEOM*
705 lwgeom_intersection_prec(const LWGEOM* geom1, const LWGEOM* geom2, double prec)
706 {
707  LWGEOM* result;
708  int32_t srid = RESULT_SRID(geom1, geom2);
709  uint8_t is3d = (FLAGS_GET_Z(geom1->flags) || FLAGS_GET_Z(geom2->flags));
710  GEOSGeometry* g1;
711  GEOSGeometry* g2;
712  GEOSGeometry* g3;
713 
714  if (srid == SRID_INVALID) return NULL;
715 
716  /* A.Intersection(Empty) == Empty */
717  if (lwgeom_is_empty(geom2)) return lwgeom_clone_deep(geom2); /* match empty type? */
718 
719  /* Empty.Intersection(A) == Empty */
720  if (lwgeom_is_empty(geom1)) return lwgeom_clone_deep(geom1); /* match empty type? */
721 
722  initGEOS(lwnotice, lwgeom_geos_error);
723 
724  if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX))) GEOS_FAIL();
725  if (!(g2 = LWGEOM2GEOS(geom2, AUTOFIX))) GEOS_FREE_AND_FAIL(g1);
726 
727  if ( prec >= 0) {
728 #if POSTGIS_GEOS_VERSION < 30900
729  lwerror("Fixed-precision intersection requires GEOS-3.9 or higher");
730  GEOS_FREE_AND_FAIL(g1, g2);
731  return NULL;
732 #else
733  g3 = GEOSIntersectionPrec(g1, g2, prec);
734 #endif
735  }
736  else
737  {
738  g3 = GEOSIntersection(g1, g2);
739  }
740 
741  if (!g3) GEOS_FREE_AND_FAIL(g1);
742  GEOSSetSRID(g3, srid);
743 
744  if (!(result = GEOS2LWGEOM(g3, is3d))) GEOS_FREE_AND_FAIL(g1, g2, g3);
745 
746  GEOS_FREE(g1, g2, g3);
747  return result;
748 }
749 
750 LWGEOM*
752 {
753  return lwgeom_linemerge_directed(geom, LW_FALSE);
754 }
755 
756 LWGEOM*
757 lwgeom_linemerge_directed(const LWGEOM* geom, int directed)
758 {
759  LWGEOM* result;
760  int32_t srid = RESULT_SRID(geom);
761  uint8_t is3d = FLAGS_GET_Z(geom->flags);
762  GEOSGeometry* g1;
763  GEOSGeometry* g3;
764 
765  if (srid == SRID_INVALID) return NULL;
766 
767  /* Empty.Linemerge() == Empty */
768  if (lwgeom_is_empty(geom)) return lwgeom_clone_deep(geom); /* match empty type to linestring? */
769 
770  initGEOS(lwnotice, lwgeom_geos_error);
771 
772  if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL();
773 
774  if (directed)
775  {
776 #if POSTGIS_GEOS_VERSION < 31100
777  lwerror("Directed line merging requires GEOS-3.11 or higher");
778  GEOS_FREE_AND_FAIL(g1);
779  return NULL;
780 #else
781  g3 = GEOSLineMergeDirected(g1);
782 #endif
783  }
784  else
785  {
786  g3 = GEOSLineMerge(g1);
787  }
788 
789  if (!g3) GEOS_FREE_AND_FAIL(g1);
790  GEOSSetSRID(g3, srid);
791 
792  if (!(result = GEOS2LWGEOM(g3, is3d)))
793  GEOS_FREE_AND_FAIL(g1, g3);
794 
795  GEOS_FREE(g1, g3);
796 
797  return result;
798 }
799 
800 LWGEOM*
802 {
803  return lwgeom_unaryunion_prec(geom, -1.0);
804 }
805 
806 LWGEOM*
807 lwgeom_unaryunion_prec(const LWGEOM* geom, double prec)
808 {
809  LWGEOM* result;
810  int32_t srid = RESULT_SRID(geom);
811  uint8_t is3d = FLAGS_GET_Z(geom->flags);
812  GEOSGeometry* g1;
813  GEOSGeometry* g3;
814 
815  if (srid == SRID_INVALID) return NULL;
816 
817  /* Empty.UnaryUnion() == Empty */
818  if (lwgeom_is_empty(geom)) return lwgeom_clone_deep(geom);
819 
820  initGEOS(lwnotice, lwgeom_geos_error);
821 
822  if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL();
823 
824  if ( prec >= 0) {
825 #if POSTGIS_GEOS_VERSION < 30900
826  lwerror("Fixed-precision union requires GEOS-3.9 or higher");
827  GEOS_FREE_AND_FAIL(g1);
828  return NULL;
829 #else
830  g3 = GEOSUnaryUnionPrec(g1, prec);
831 #endif
832  }
833  else
834  {
835  g3 = GEOSUnaryUnion(g1);
836  }
837 
838  if (!g3) GEOS_FREE_AND_FAIL(g1);
839  GEOSSetSRID(g3, srid);
840 
841  if (!(result = GEOS2LWGEOM(g3, is3d)))
842  GEOS_FREE_AND_FAIL(g1, g3);
843 
844  GEOS_FREE(g1, g3);
845 
846  return result;
847 }
848 
849 LWGEOM*
850 lwgeom_difference(const LWGEOM* geom1, const LWGEOM* geom2)
851 {
852  return lwgeom_difference_prec(geom1, geom2, -1.0);
853 }
854 
855 LWGEOM*
856 lwgeom_difference_prec(const LWGEOM* geom1, const LWGEOM* geom2, double prec)
857 {
858  LWGEOM* result;
859  int32_t srid = RESULT_SRID(geom1, geom2);
860  uint8_t is3d = (FLAGS_GET_Z(geom1->flags) || FLAGS_GET_Z(geom2->flags));
861  GEOSGeometry *g1, *g2, *g3;
862 
863  if (srid == SRID_INVALID) return NULL;
864 
865  /* A.Intersection(Empty) == Empty */
866  if (lwgeom_is_empty(geom2)) return lwgeom_clone_deep(geom1); /* match empty type? */
867 
868  /* Empty.Intersection(A) == Empty */
869  if (lwgeom_is_empty(geom1)) return lwgeom_clone_deep(geom1); /* match empty type? */
870 
871  initGEOS(lwnotice, lwgeom_geos_error);
872 
873  if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX))) GEOS_FAIL();
874  if (!(g2 = LWGEOM2GEOS(geom2, AUTOFIX))) GEOS_FREE_AND_FAIL(g1);
875 
876  if ( prec >= 0) {
877 #if POSTGIS_GEOS_VERSION < 30900
878  lwerror("Fixed-precision difference requires GEOS-3.9 or higher");
879  GEOS_FREE_AND_FAIL(g1, g2);
880  return NULL;
881 #else
882  g3 = GEOSDifferencePrec(g1, g2, prec);
883 #endif
884  }
885  else
886  {
887  g3 = GEOSDifference(g1, g2);
888  }
889 
890  if (!g3) GEOS_FREE_AND_FAIL(g1, g2);
891  GEOSSetSRID(g3, srid);
892 
893  if (!(result = GEOS2LWGEOM(g3, is3d)))
894  GEOS_FREE_AND_FAIL(g1, g2, g3);
895 
896  GEOS_FREE(g1, g2, g3);
897  return result;
898 }
899 
900 LWGEOM*
901 lwgeom_symdifference(const LWGEOM* geom1, const LWGEOM* geom2)
902 {
903  return lwgeom_symdifference_prec(geom1, geom2, -1.0);
904 }
905 
906 LWGEOM*
907 lwgeom_symdifference_prec(const LWGEOM* geom1, const LWGEOM* geom2, double prec)
908 {
909  LWGEOM* result;
910  int32_t srid = RESULT_SRID(geom1, geom2);
911  uint8_t is3d = (FLAGS_GET_Z(geom1->flags) || FLAGS_GET_Z(geom2->flags));
912  GEOSGeometry *g1, *g2, *g3;
913 
914  if (srid == SRID_INVALID) return NULL;
915 
916  /* A.SymDifference(Empty) == A */
917  if (lwgeom_is_empty(geom2)) return lwgeom_clone_deep(geom1);
918 
919  /* Empty.DymDifference(B) == B */
920  if (lwgeom_is_empty(geom1)) return lwgeom_clone_deep(geom2);
921 
922  initGEOS(lwnotice, lwgeom_geos_error);
923 
924  if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX))) GEOS_FAIL();
925  if (!(g2 = LWGEOM2GEOS(geom2, AUTOFIX))) GEOS_FREE_AND_FAIL(g1);
926 
927  if ( prec >= 0) {
928 #if POSTGIS_GEOS_VERSION < 30900
929  lwerror("Fixed-precision difference requires GEOS-3.9 or higher");
930  GEOS_FREE_AND_FAIL(g1, g2);
931  return NULL;
932 #else
933  g3 = GEOSSymDifferencePrec(g1, g2, prec);
934 #endif
935  }
936  else
937  {
938  g3 = GEOSSymDifference(g1, g2);
939  }
940 
941  if (!g3) GEOS_FREE_AND_FAIL(g1, g2);
942  GEOSSetSRID(g3, srid);
943 
944  if (!(result = GEOS2LWGEOM(g3, is3d)))
945  GEOS_FREE_AND_FAIL(g1, g2, g3);
946 
947  GEOS_FREE(g1, g2, g3);
948  return result;
949 }
950 
951 LWGEOM*
953 {
954  LWGEOM* result;
955  int32_t srid = RESULT_SRID(geom);
956  uint8_t is3d = FLAGS_GET_Z(geom->flags);
957  GEOSGeometry *g1, *g3;
958 
959  if (srid == SRID_INVALID) return NULL;
960 
961  if (lwgeom_is_empty(geom))
962  {
963  LWPOINT* lwp = lwpoint_construct_empty(srid, is3d, lwgeom_has_m(geom));
964  return lwpoint_as_lwgeom(lwp);
965  }
966 
967  initGEOS(lwnotice, lwgeom_geos_error);
968 
969  if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL();
970 
971  g3 = GEOSGetCentroid(g1);
972 
973  if (!g3) GEOS_FREE_AND_FAIL(g1);
974  GEOSSetSRID(g3, srid);
975 
976  if (!(result = GEOS2LWGEOM(g3, is3d)))
977  GEOS_FREE_AND_FAIL(g1);
978 
979  GEOS_FREE(g1, g3);
980 
981  return result;
982 }
983 
984 LWGEOM*
985 lwgeom_reduceprecision(const LWGEOM* geom, double gridSize)
986 {
987 #if POSTGIS_GEOS_VERSION < 30900
988  lwerror("Precision reduction requires GEOS-3.9 or higher");
989  return NULL;
990 #else
991 
992  LWGEOM* result;
993  int32_t srid = RESULT_SRID(geom);
994  uint8_t is3d = FLAGS_GET_Z(geom->flags);
995  GEOSGeometry *g1, *g3;
996 
997  if (srid == SRID_INVALID) return NULL;
998 
999  if (lwgeom_is_empty(geom))
1000  return lwgeom_clone_deep(geom);
1001 
1002  initGEOS(lwnotice, lwgeom_geos_error);
1003 
1004  if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL();
1005 
1006  g3 = GEOSGeom_setPrecision(g1, gridSize, 0);
1007 
1008  if (!g3) GEOS_FREE_AND_FAIL(g1);
1009  GEOSSetSRID(g3, srid);
1010 
1011  if (!(result = GEOS2LWGEOM(g3, is3d)))
1012  GEOS_FREE_AND_FAIL(g1);
1013 
1014  GEOS_FREE(g1, g3);
1015 
1016  return result;
1017 #endif
1018 }
1019 
1020 LWGEOM *
1022 {
1023  LWGEOM *result;
1024  int32_t srid = RESULT_SRID(geom);
1025  uint8_t is3d = FLAGS_GET_Z(geom->flags);
1026  GEOSGeometry *g1, *g3;
1027 
1028  if (srid == SRID_INVALID) return NULL;
1029 
1030  if (lwgeom_is_empty(geom))
1031  {
1032  LWPOINT *lwp = lwpoint_construct_empty(srid, is3d, lwgeom_has_m(geom));
1033  return lwpoint_as_lwgeom(lwp);
1034  }
1035 
1036  initGEOS(lwnotice, lwgeom_geos_error);
1037 
1038  if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL();
1039 
1040  g3 = GEOSPointOnSurface(g1);
1041 
1042  if (!g3) GEOS_FREE_AND_FAIL(g1);
1043  GEOSSetSRID(g3, srid);
1044 
1045  if (!(result = GEOS2LWGEOM(g3, is3d)))
1046  GEOS_FREE_AND_FAIL(g1, g3);
1047 
1048  GEOS_FREE(g1, g3);
1049 
1050  return result;
1051 }
1052 
1053 LWGEOM*
1054 lwgeom_union(const LWGEOM* g1, const LWGEOM* g2)
1055 {
1056  return lwgeom_union_prec(g1, g2, -1.0);
1057 }
1058 
1059 LWGEOM*
1060 lwgeom_union_prec(const LWGEOM* geom1, const LWGEOM* geom2, double gridSize)
1061 {
1062  LWGEOM* result;
1063  int32_t srid = RESULT_SRID(geom1, geom2);
1064  uint8_t is3d = (FLAGS_GET_Z(geom1->flags) || FLAGS_GET_Z(geom2->flags));
1065  GEOSGeometry *g1, *g2, *g3;
1066 
1067  if (srid == SRID_INVALID) return NULL;
1068 
1069  /* A.Union(empty) == A */
1070  if (lwgeom_is_empty(geom1)) return lwgeom_clone_deep(geom2);
1071 
1072  /* B.Union(empty) == B */
1073  if (lwgeom_is_empty(geom2)) return lwgeom_clone_deep(geom1);
1074 
1075  initGEOS(lwnotice, lwgeom_geos_error);
1076 
1077  if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX))) GEOS_FAIL();
1078  if (!(g2 = LWGEOM2GEOS(geom2, AUTOFIX))) GEOS_FREE_AND_FAIL(g1);
1079 
1080  if ( gridSize >= 0) {
1081 #if POSTGIS_GEOS_VERSION < 30900
1082  lwerror("Fixed-precision union requires GEOS-3.9 or higher");
1083  GEOS_FREE_AND_FAIL(g1, g2);
1084  return NULL;
1085 #else
1086  g3 = GEOSUnionPrec(g1, g2, gridSize);
1087 #endif
1088  }
1089  else
1090  {
1091  g3 = GEOSUnion(g1, g2);
1092  }
1093 
1094  if (!g3) GEOS_FREE_AND_FAIL(g1, g2);
1095  GEOSSetSRID(g3, srid);
1096 
1097  if (!(result = GEOS2LWGEOM(g3, is3d)))
1098  GEOS_FREE_AND_FAIL(g1, g2, g3);
1099 
1100  GEOS_FREE(g1, g2, g3);
1101  return result;
1102 }
1103 
1104 LWGEOM *
1105 lwgeom_clip_by_rect(const LWGEOM *geom1, double x1, double y1, double x2, double y2)
1106 {
1107  LWGEOM *result;
1108  GEOSGeometry *g1, *g3;
1109  int is3d;
1110 
1111  /* A.Intersection(Empty) == Empty */
1112  if ( lwgeom_is_empty(geom1) )
1113  return lwgeom_clone_deep(geom1);
1114 
1115  is3d = FLAGS_GET_Z(geom1->flags);
1116 
1117  initGEOS(lwnotice, lwgeom_geos_error);
1118 
1119  if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX)))
1120  GEOS_FAIL_DEBUG();
1121 
1122  if (!(g3 = GEOSClipByRect(g1, x1, y1, x2, y2)))
1124 
1125  GEOS_FREE(g1);
1126  result = GEOS2LWGEOM(g3, is3d);
1127  GEOS_FREE(g3);
1128 
1129  if (!result)
1130  GEOS_FAIL_DEBUG();
1131 
1132  result->srid = geom1->srid;
1133 
1134  return result;
1135 }
1136 
1137 /* ------------ BuildArea stuff ---------------------------------------------------------------------{ */
1138 #if POSTGIS_GEOS_VERSION < 30800
1139 typedef struct Face_t
1140 {
1141  const GEOSGeometry* geom;
1142  GEOSGeometry* env;
1143  double envarea;
1144  struct Face_t* parent; /* if this face is an hole of another one, or NULL */
1145 } Face;
1146 
1147 static Face* newFace(const GEOSGeometry* g);
1148 static void delFace(Face* f);
1149 static unsigned int countParens(const Face* f);
1150 static void findFaceHoles(Face** faces, int nfaces);
1151 
1152 static Face*
1153 newFace(const GEOSGeometry* g)
1154 {
1155  Face* f = lwalloc(sizeof(Face));
1156  f->geom = g;
1157  f->env = GEOSEnvelope(f->geom);
1158  GEOSArea(f->env, &f->envarea);
1159  f->parent = NULL;
1160  return f;
1161 }
1162 
1163 static unsigned int
1164 countParens(const Face* f)
1165 {
1166  unsigned int pcount = 0;
1167  while (f->parent)
1168  {
1169  ++pcount;
1170  f = f->parent;
1171  }
1172  return pcount;
1173 }
1174 
1175 /* Destroy the face and release memory associated with it */
1176 static void
1177 delFace(Face* f)
1178 {
1179  GEOSGeom_destroy(f->env);
1180  lwfree(f);
1181 }
1182 
1183 static int
1184 compare_by_envarea(const void* g1, const void* g2)
1185 {
1186  Face* f1 = *(Face**)g1;
1187  Face* f2 = *(Face**)g2;
1188  double n1 = f1->envarea;
1189  double n2 = f2->envarea;
1190 
1191  if (n1 < n2) return 1;
1192  if (n1 > n2) return -1;
1193  return 0;
1194 }
1195 
1196 /* Find holes of each face */
1197 static void
1198 findFaceHoles(Face** faces, int nfaces)
1199 {
1200  int i, j, h;
1201 
1202  /* We sort by envelope area so that we know holes are only after their shells */
1203  qsort(faces, nfaces, sizeof(Face*), compare_by_envarea);
1204  for (i = 0; i < nfaces; ++i)
1205  {
1206  Face* f = faces[i];
1207  int nholes = GEOSGetNumInteriorRings(f->geom);
1208  LWDEBUGF(2, "Scanning face %d with env area %g and %d holes", i, f->envarea, nholes);
1209  for (h = 0; h < nholes; ++h)
1210  {
1211  const GEOSGeometry* hole = GEOSGetInteriorRingN(f->geom, h);
1212  LWDEBUGF(2,
1213  "Looking for hole %d/%d of face %d among %d other faces",
1214  h + 1,
1215  nholes,
1216  i,
1217  nfaces - i - 1);
1218  for (j = i + 1; j < nfaces; ++j)
1219  {
1220  const GEOSGeometry* f2er;
1221  Face* f2 = faces[j];
1222  if (f2->parent) continue; /* hole already assigned */
1223  f2er = GEOSGetExteriorRing(f2->geom);
1224  /* TODO: can be optimized as the ring would have the same vertices, possibly in
1225  * different order. Maybe comparing number of points could already be useful. */
1226  if (GEOSEquals(f2er, hole))
1227  {
1228  LWDEBUGF(2, "Hole %d/%d of face %d is face %d", h + 1, nholes, i, j);
1229  f2->parent = f;
1230  break;
1231  }
1232  }
1233  }
1234  }
1235 }
1236 
1237 static GEOSGeometry*
1238 collectFacesWithEvenAncestors(Face** faces, int nfaces)
1239 {
1240  GEOSGeometry** geoms = lwalloc(sizeof(GEOSGeometry*) * nfaces);
1241  GEOSGeometry* ret;
1242  unsigned int ngeoms = 0;
1243  int i;
1244 
1245  for (i = 0; i < nfaces; ++i)
1246  {
1247  Face* f = faces[i];
1248  if (countParens(f) % 2) continue; /* we skip odd parents geoms */
1249  geoms[ngeoms++] = GEOSGeom_clone(f->geom);
1250  }
1251 
1252  ret = GEOSGeom_createCollection(GEOS_MULTIPOLYGON, geoms, ngeoms);
1253  lwfree(geoms);
1254  return ret;
1255 }
1256 
1257 GEOSGeometry*
1258 LWGEOM_GEOS_buildArea(const GEOSGeometry* geom_in)
1259 {
1260  GEOSGeometry* tmp;
1261  GEOSGeometry *geos_result, *shp;
1262  GEOSGeometry const* vgeoms[1];
1263  uint32_t i, ngeoms;
1264  int srid = GEOSGetSRID(geom_in);
1265  Face** geoms;
1266 #if POSTGIS_DEBUG_LEVEL >= 3
1267  LWGEOM *geos_geom;
1268  char *geom_ewkt;
1269 #endif
1270 
1271  vgeoms[0] = geom_in;
1272  geos_result = GEOSPolygonize(vgeoms, 1);
1273 
1274  LWDEBUGF(3, "GEOSpolygonize returned @ %p", geos_result);
1275 
1276  /* Null return from GEOSpolygonize (an exception) */
1277  if (!geos_result) return 0;
1278 
1279  /* We should now have a collection */
1280 #if PARANOIA_LEVEL > 0
1281  if (GEOSGeomTypeId(geos_result) != COLLECTIONTYPE)
1282  {
1283  GEOSGeom_destroy(geos_result);
1284  lwerror("%s [%d] Unexpected return from GEOSpolygonize", __FILE__, __LINE__);
1285  return 0;
1286  }
1287 #endif
1288 
1289  ngeoms = GEOSGetNumGeometries(geos_result);
1290 
1291 #if POSTGIS_DEBUG_LEVEL >= 3
1292  LWDEBUGF(3, "GEOSpolygonize: ngeoms in polygonize output: %d", ngeoms);
1293  geos_geom = GEOS2LWGEOM(geos_result, 0);
1294  geom_ewkt = lwgeom_to_ewkt(geos_geom);
1295  LWDEBUGF(3, "GEOSpolygonize: polygonized:%s", geom_ewkt);
1296  lwgeom_free(geos_geom);
1297  lwfree(geom_ewkt);
1298 #endif
1299 
1300  /* No geometries in collection, early out */
1301  if (ngeoms == 0)
1302  {
1303  GEOSSetSRID(geos_result, srid);
1304  return geos_result;
1305  }
1306 
1307  /* Return first geometry if we only have one in collection, to avoid the unnecessary Geometry clone below. */
1308  if (ngeoms == 1)
1309  {
1310  tmp = (GEOSGeometry*)GEOSGetGeometryN(geos_result, 0);
1311  if (!tmp)
1312  {
1313  GEOSGeom_destroy(geos_result);
1314  return 0; /* exception */
1315  }
1316  shp = GEOSGeom_clone(tmp);
1317  GEOSGeom_destroy(geos_result); /* only safe after the clone above */
1318  GEOSSetSRID(shp, srid);
1319  return shp;
1320  }
1321 
1322  LWDEBUGF(2, "Polygonize returned %d geoms", ngeoms);
1323 
1324  /*
1325  * Polygonizer returns a polygon for each face in the built topology.
1326  *
1327  * This means that for any face with holes we'll have other faces representing each hole. We can imagine a
1328  * parent-child relationship between these faces.
1329  *
1330  * In order to maximize the number of visible rings in output we only use those faces which have an even number
1331  * of parents.
1332  *
1333  * Example:
1334  *
1335  * +---------------+
1336  * | L0 | L0 has no parents
1337  * | +---------+ |
1338  * | | L1 | | L1 is an hole of L0
1339  * | | +---+ | |
1340  * | | |L2 | | | L2 is an hole of L1 (which is an hole of L0)
1341  * | | | | | |
1342  * | | +---+ | |
1343  * | +---------+ |
1344  * | |
1345  * +---------------+
1346  *
1347  * See http://trac.osgeo.org/postgis/ticket/1806
1348  *
1349  */
1350 
1351  /* Prepare face structures for later analysis */
1352  geoms = lwalloc(sizeof(Face**) * ngeoms);
1353  for (i = 0; i < ngeoms; ++i)
1354  geoms[i] = newFace(GEOSGetGeometryN(geos_result, i));
1355 
1356  /* Find faces representing other faces holes */
1357  findFaceHoles(geoms, ngeoms);
1358 
1359  /* Build a MultiPolygon composed only by faces with an even number of ancestors */
1360  tmp = collectFacesWithEvenAncestors(geoms, ngeoms);
1361 
1362  /* Cleanup face structures */
1363  for (i = 0; i < ngeoms; ++i)
1364  delFace(geoms[i]);
1365  lwfree(geoms);
1366 
1367  /* Faces referenced memory owned by geos_result. It is safe to destroy geos_result after deleting them. */
1368  GEOSGeom_destroy(geos_result);
1369 
1370  /* Run a single overlay operation to dissolve shared edges */
1371  shp = GEOSUnionCascaded(tmp);
1372  if (!shp)
1373  {
1374  GEOSGeom_destroy(tmp);
1375  return 0; /* exception */
1376  }
1377 
1378  GEOSGeom_destroy(tmp);
1379 
1380  GEOSSetSRID(shp, srid);
1381 
1382  return shp;
1383 }
1384 #endif
1385 
1386 LWGEOM*
1388 {
1389  LWGEOM* result;
1390  int32_t srid = RESULT_SRID(geom);
1391  uint8_t is3d = FLAGS_GET_Z(geom->flags);
1392  GEOSGeometry *g1, *g3;
1393 
1394  if (srid == SRID_INVALID) return NULL;
1395 
1396  /* Can't build an area from an empty! */
1397  if (lwgeom_is_empty(geom)) return (LWGEOM*)lwpoly_construct_empty(srid, is3d, 0);
1398 
1399  initGEOS(lwnotice, lwgeom_geos_error);
1400 
1401  if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL();
1402 
1403 #if POSTGIS_GEOS_VERSION < 30800
1404  g3 = LWGEOM_GEOS_buildArea(g1);
1405 #else
1406  g3 = GEOSBuildArea(g1);
1407 #endif
1408 
1409  if (!g3) GEOS_FREE_AND_FAIL(g1);
1410  GEOSSetSRID(g3, srid);
1411 
1412  /* If no geometries are in result collection, return NULL */
1413  if (GEOSGetNumGeometries(g3) == 0)
1414  {
1415  GEOS_FREE(g1);
1416  return NULL;
1417  }
1418 
1419  if (!(result = GEOS2LWGEOM(g3, is3d)))
1420  GEOS_FREE_AND_FAIL(g1, g3);
1421 
1422  GEOS_FREE(g1, g3);
1423 
1424  return result;
1425 }
1426 
1427 /* ------------ end of BuildArea stuff ---------------------------------------------------------------------} */
1428 
1429 int
1431 {
1432  GEOSGeometry* g;
1433  int simple;
1434 
1435  /* Empty is always simple */
1436  if (lwgeom_is_empty(geom)) return LW_TRUE;
1437 
1438  initGEOS(lwnotice, lwgeom_geos_error);
1439 
1440  if (!(g = LWGEOM2GEOS(geom, AUTOFIX))) return -1;
1441 
1442  simple = GEOSisSimple(g);
1443  GEOSGeom_destroy(g);
1444 
1445  if (simple == 2) /* exception thrown */
1446  {
1447  lwerror("lwgeom_is_simple: %s", lwgeom_geos_errmsg);
1448  return -1;
1449  }
1450 
1451  return simple ? LW_TRUE : LW_FALSE;
1452 }
1453 
1454 LWGEOM*
1456 {
1457  LWGEOM* result;
1458  int32_t srid = RESULT_SRID(geom);
1459  uint8_t is3d = FLAGS_GET_Z(geom->flags);
1460  GEOSGeometry* g;
1461 
1462  if (srid == SRID_INVALID) return NULL;
1463 
1464  initGEOS(lwnotice, lwgeom_geos_error);
1465 
1466  if (!(g = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL();
1467 
1468  if (!g) GEOS_FREE_AND_FAIL(g);
1469  GEOSSetSRID(g, srid);
1470 
1471  if (!(result = GEOS2LWGEOM(g, is3d)))
1472  GEOS_FREE_AND_FAIL(g);
1473 
1474  GEOS_FREE(g);
1475 
1476  return result;
1477 }
1478 
1479 LWGEOM*
1480 lwgeom_snap(const LWGEOM* geom1, const LWGEOM* geom2, double tolerance)
1481 {
1482  LWGEOM* result;
1483  int32_t srid = RESULT_SRID(geom1, geom2);
1484  uint8_t is3d = (FLAGS_GET_Z(geom1->flags) || FLAGS_GET_Z(geom2->flags));
1485  GEOSGeometry *g1, *g2, *g3;
1486 
1487  if (srid == SRID_INVALID) return NULL;
1488 
1489  initGEOS(lwnotice, lwgeom_geos_error);
1490 
1491  if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX))) GEOS_FAIL();
1492  if (!(g2 = LWGEOM2GEOS(geom2, AUTOFIX))) GEOS_FREE_AND_FAIL(g1);
1493 
1494  g3 = GEOSSnap(g1, g2, tolerance);
1495 
1496  if (!g3) GEOS_FREE_AND_FAIL(g1, g2);
1497  GEOSSetSRID(g3, srid);
1498 
1499  if (!(result = GEOS2LWGEOM(g3, is3d)))
1500  GEOS_FREE_AND_FAIL(g1, g2, g3);
1501 
1502  GEOS_FREE(g1, g2, g3);
1503  return result;
1504 }
1505 
1506 LWGEOM*
1507 lwgeom_sharedpaths(const LWGEOM* geom1, const LWGEOM* geom2)
1508 {
1509  LWGEOM* result;
1510  int32_t srid = RESULT_SRID(geom1, geom2);
1511  uint8_t is3d = (FLAGS_GET_Z(geom1->flags) || FLAGS_GET_Z(geom2->flags));
1512  GEOSGeometry *g1, *g2, *g3;
1513 
1514  if (srid == SRID_INVALID) return NULL;
1515 
1516  initGEOS(lwnotice, lwgeom_geos_error);
1517 
1518  if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX))) GEOS_FAIL();
1519  if (!(g2 = LWGEOM2GEOS(geom2, AUTOFIX))) GEOS_FREE_AND_FAIL(g1);
1520 
1521  g3 = GEOSSharedPaths(g1, g2);
1522 
1523  if (!g3) GEOS_FREE_AND_FAIL(g1, g2);
1524  GEOSSetSRID(g3, srid);
1525 
1526  if (!(result = GEOS2LWGEOM(g3, is3d)))
1527  GEOS_FREE_AND_FAIL(g1, g2, g3);
1528 
1529  GEOS_FREE(g1, g2, g3);
1530  return result;
1531 }
1532 
1533 static LWGEOM *
1534 lwline_offsetcurve(const LWLINE *lwline, double size, int quadsegs, int joinStyle, double mitreLimit)
1535 {
1536  LWGEOM* result;
1537  LWGEOM* geom = lwline_as_lwgeom(lwline);
1538  int32_t srid = RESULT_SRID(geom);
1539  uint8_t is3d = FLAGS_GET_Z(geom->flags);
1540  GEOSGeometry *g1, *g3;
1541 
1542  if (srid == SRID_INVALID) return NULL;
1543 
1544  initGEOS(lwnotice, lwgeom_geos_error);
1545 
1546  if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL();
1547 
1548  g3 = GEOSOffsetCurve(g1, size, quadsegs, joinStyle, mitreLimit);
1549 
1550  if (!g3)
1551  {
1552  GEOS_FREE(g1);
1553  return NULL;
1554  }
1555 
1556  GEOSSetSRID(g3, srid);
1557 
1558  if (!(result = GEOS2LWGEOM(g3, is3d)))
1559  GEOS_FREE_AND_FAIL(g1, g3);
1560 
1561  GEOS_FREE(g1, g3);
1562  return result;
1563 }
1564 
1565 static LWGEOM *
1566 lwcollection_offsetcurve(const LWCOLLECTION *col, double size, int quadsegs, int joinStyle, double mitreLimit)
1567 {
1568  const LWGEOM *geom = lwcollection_as_lwgeom(col);
1569  int32_t srid = RESULT_SRID(geom);
1570  uint8_t is3d = FLAGS_GET_Z(col->flags);
1572  LWGEOM *tmp;
1573  uint32_t i;
1574  if (srid == SRID_INVALID) return NULL;
1575 
1577 
1578  for (i = 0; i < col->ngeoms; i++)
1579  {
1580  tmp = lwgeom_offsetcurve(col->geoms[i], size, quadsegs, joinStyle, mitreLimit);
1581 
1582  if (!tmp)
1583  {
1585  return NULL;
1586  }
1587 
1588  if (!lwgeom_is_empty(tmp))
1589  {
1590  if (lwgeom_is_collection(tmp))
1592  else
1594 
1595  if (!result)
1596  {
1597  lwgeom_free(tmp);
1598  return NULL;
1599  }
1600  }
1601  }
1602 
1603  if (result->ngeoms == 1)
1604  {
1605  tmp = result->geoms[0];
1607  return tmp;
1608  }
1609  else
1611 }
1612 
1613 LWGEOM*
1614 lwgeom_offsetcurve(const LWGEOM* geom, double size, int quadsegs, int joinStyle, double mitreLimit)
1615 {
1616  int32_t srid = RESULT_SRID(geom);
1617  LWGEOM *result = NULL;
1618  LWGEOM *noded = NULL;
1619  if (srid == SRID_INVALID) return NULL;
1620 
1621  if (lwgeom_dimension(geom) != 1)
1622  {
1623  lwerror("%s: input is not linear", __func__, lwtype_name(geom->type));
1624  return NULL;
1625  }
1626 
1627  while (!result)
1628  {
1629  switch (geom->type)
1630  {
1631  case LINETYPE:
1632  result = lwline_offsetcurve(lwgeom_as_lwline(geom), size, quadsegs, joinStyle, mitreLimit);
1633  break;
1634  case COLLECTIONTYPE:
1635  case MULTILINETYPE:
1636  result = lwcollection_offsetcurve(lwgeom_as_lwcollection(geom), size, quadsegs, joinStyle, mitreLimit);
1637  break;
1638  default:
1639  lwerror("%s: unsupported geometry type: %s", __func__, lwtype_name(geom->type));
1640  return NULL;
1641  }
1642 
1643  if (result)
1644  {
1645  if (noded) lwgeom_free(noded);
1646  return result;
1647  }
1648  else if (!noded)
1649  {
1650  noded = lwgeom_node(geom);
1651  if (!noded)
1652  {
1653  lwerror("lwgeom_offsetcurve: cannot node input");
1654  return NULL;
1655  }
1656  geom = noded;
1657  }
1658  else
1659  {
1660  lwgeom_free(noded);
1661  lwerror("lwgeom_offsetcurve: noded geometry cannot be offset");
1662  return NULL;
1663  }
1664  }
1665 
1666  return result;
1667 }
1668 
1669 LWMPOINT*
1670 lwpoly_to_points(const LWPOLY* lwpoly, uint32_t npoints, int32_t seed)
1671 {
1672  double area, bbox_area, bbox_width, bbox_height;
1673  GBOX bbox;
1674  const LWGEOM* lwgeom = (LWGEOM*)lwpoly;
1675  uint32_t sample_npoints, sample_sqrt, sample_width, sample_height;
1676  double sample_cell_size;
1677  uint32_t i, j, n;
1678  uint32_t iterations = 0;
1679  uint32_t npoints_generated = 0;
1680  uint32_t npoints_tested = 0;
1681  GEOSGeometry* g;
1682  const GEOSPreparedGeometry* gprep;
1683  GEOSGeometry* gpt;
1684  GEOSCoordSequence* gseq;
1685  LWMPOINT* mpt;
1686  int32_t srid = lwgeom_get_srid(lwgeom);
1687  int done = 0;
1688  int* cells;
1689  const size_t size = 2 * sizeof(int);
1690  char tmp[2 * sizeof(int)];
1691  const size_t stride = 2 * sizeof(int);
1692 
1693  if (lwgeom_get_type(lwgeom) != POLYGONTYPE)
1694  {
1695  lwerror("%s: only polygons supported", __func__);
1696  return NULL;
1697  }
1698 
1699  if (npoints == 0 || lwgeom_is_empty(lwgeom)) return NULL;
1700 
1701  if (!lwpoly->bbox)
1702  lwgeom_calculate_gbox(lwgeom, &bbox);
1703  else
1704  bbox = *(lwpoly->bbox);
1705 
1706  area = lwpoly_area(lwpoly);
1707  bbox_width = bbox.xmax - bbox.xmin;
1708  bbox_height = bbox.ymax - bbox.ymin;
1709  bbox_area = bbox_width * bbox_height;
1710 
1711  if (area == 0.0 || bbox_area == 0.0)
1712  {
1713  lwerror("%s: zero area input polygon, TBD", __func__);
1714  return NULL;
1715  }
1716 
1717  /* Gross up our test set a bit (but not too much) to increase
1718  * odds of getting coverage in one pass */
1719  sample_npoints = npoints * FP_MIN(bbox_area / area, 10000.0);
1720 
1721  /* We're going to generate points using a sample grid as described
1722  * http://lin-ear-th-inking.blogspot.ca/2010/05/more-random-points-in-jts.html to try and get a more uniform
1723  * "random" set of points. So we have to figure out how to stick a grid into our box */
1724  sample_sqrt = lround(sqrt(sample_npoints));
1725  if (sample_sqrt == 0)
1726  sample_sqrt = 1;
1727 
1728  /* Calculate the grids we're going to randomize within */
1729  if (bbox_width > bbox_height)
1730  {
1731  sample_width = sample_sqrt;
1732  sample_height = ceil((double)sample_npoints / (double)sample_width);
1733  sample_cell_size = bbox_width / sample_width;
1734  }
1735  else
1736  {
1737  sample_height = sample_sqrt;
1738  sample_width = ceil((double)sample_npoints / (double)sample_height);
1739  sample_cell_size = bbox_height / sample_height;
1740  }
1741 
1742  /* Prepare the polygon for fast true/false testing */
1743  initGEOS(lwnotice, lwgeom_geos_error);
1744  g = (GEOSGeometry*)LWGEOM2GEOS(lwgeom, 0);
1745  if (!g)
1746  {
1747  lwerror("%s: Geometry could not be converted to GEOS: %s", __func__, lwgeom_geos_errmsg);
1748  return NULL;
1749  }
1750  gprep = GEOSPrepare(g);
1751 
1752  /* Get an empty multi-point ready to return */
1753  mpt = lwmpoint_construct_empty(srid, 0, 0);
1754 
1755  /* Initiate random number generator.
1756  * Repeatable numbers are generated with seed values >= 1.
1757  * When seed is zero and has not previously been set, it is based on
1758  * Unix time (seconds) and process ID. */
1759  lwrandom_set_seed(seed);
1760 
1761  /* Now we fill in an array of cells, and then shuffle that array, */
1762  /* so we can visit the cells in random order to avoid visual ugliness */
1763  /* caused by visiting them sequentially */
1764  cells = lwalloc(2 * sizeof(int) * sample_height * sample_width);
1765  for (i = 0; i < sample_width; i++)
1766  {
1767  for (j = 0; j < sample_height; j++)
1768  {
1769  cells[2 * (i * sample_height + j)] = i;
1770  cells[2 * (i * sample_height + j) + 1] = j;
1771  }
1772  }
1773 
1774  /* Fisher-Yates shuffle */
1775  n = sample_height * sample_width;
1776  if (n > 1)
1777  {
1778  for (i = n - 1; i > 0; i--)
1779  {
1780  size_t j = (size_t)(lwrandom_uniform() * (i + 1));
1781 
1782  memcpy(tmp, (char *)cells + j * stride, size);
1783  memcpy((char *)cells + j * stride, (char *)cells + i * stride, size);
1784  memcpy((char *)cells + i * stride, tmp, size);
1785  }
1786  }
1787 
1788  /* Start testing points */
1789  while (npoints_generated < npoints)
1790  {
1791  iterations++;
1792  for (i = 0; i < sample_width * sample_height; i++)
1793  {
1794  int contains = 0;
1795  double y = bbox.ymin + cells[2 * i] * sample_cell_size;
1796  double x = bbox.xmin + cells[2 * i + 1] * sample_cell_size;
1797  x += lwrandom_uniform() * sample_cell_size;
1798  y += lwrandom_uniform() * sample_cell_size;
1799  if (x >= bbox.xmax || y >= bbox.ymax) continue;
1800 
1801  gseq = GEOSCoordSeq_create(1, 2);
1802 #if POSTGIS_GEOS_VERSION < 30800
1803  GEOSCoordSeq_setX(gseq, 0, x);
1804  GEOSCoordSeq_setY(gseq, 0, y);
1805 #else
1806  GEOSCoordSeq_setXY(gseq, 0, x, y);
1807 #endif
1808  gpt = GEOSGeom_createPoint(gseq);
1809 
1810  contains = GEOSPreparedIntersects(gprep, gpt);
1811 
1812  GEOSGeom_destroy(gpt);
1813 
1814  if (contains == 2)
1815  {
1816  GEOSPreparedGeom_destroy(gprep);
1817  GEOSGeom_destroy(g);
1818  lwerror("%s: GEOS exception on PreparedContains: %s", __func__, lwgeom_geos_errmsg);
1819  return NULL;
1820  }
1821  if (contains)
1822  {
1823  npoints_generated++;
1824  mpt = lwmpoint_add_lwpoint(mpt, lwpoint_make2d(srid, x, y));
1825  if (npoints_generated == npoints)
1826  {
1827  done = 1;
1828  break;
1829  }
1830  }
1831 
1832  /* Short-circuit check for ctrl-c occasionally */
1833  npoints_tested++;
1834  if (npoints_tested % 10000 == 0)
1835  LW_ON_INTERRUPT(GEOSPreparedGeom_destroy(gprep); GEOSGeom_destroy(g); return NULL);
1836 
1837  if (done) break;
1838  }
1839  if (done || iterations > 100) break;
1840  }
1841 
1842  GEOSPreparedGeom_destroy(gprep);
1843  GEOSGeom_destroy(g);
1844  lwfree(cells);
1845 
1846  return mpt;
1847 }
1848 
1849 /* Allocate points to sub-geometries by area, then call lwgeom_poly_to_points and bundle up final result in a single
1850  * multipoint. */
1851 LWMPOINT*
1852 lwmpoly_to_points(const LWMPOLY* lwmpoly, uint32_t npoints, int32_t seed)
1853 {
1854  const LWGEOM* lwgeom = (LWGEOM*)lwmpoly;
1855  double area;
1856  uint32_t i;
1857  LWMPOINT* mpt = NULL;
1858 
1859  if (lwgeom_get_type(lwgeom) != MULTIPOLYGONTYPE)
1860  {
1861  lwerror("%s: only multipolygons supported", __func__);
1862  return NULL;
1863  }
1864  if (npoints == 0 || lwgeom_is_empty(lwgeom)) return NULL;
1865 
1866  area = lwgeom_area(lwgeom);
1867 
1868  for (i = 0; i < lwmpoly->ngeoms; i++)
1869  {
1870  double sub_area = lwpoly_area(lwmpoly->geoms[i]);
1871  int sub_npoints = lround(npoints * sub_area / area);
1872  if (sub_npoints > 0)
1873  {
1874  LWMPOINT* sub_mpt = lwpoly_to_points(lwmpoly->geoms[i], sub_npoints, seed);
1875  if (!mpt)
1876  mpt = sub_mpt;
1877  else
1878  {
1879  uint32_t j;
1880  for (j = 0; j < sub_mpt->ngeoms; j++)
1881  mpt = lwmpoint_add_lwpoint(mpt, sub_mpt->geoms[j]);
1882  /* Just free the shell, leave the underlying lwpoints alone, as they are now owned by
1883  * the returning multipoint */
1884  lwfree(sub_mpt->geoms);
1885  lwgeom_release((LWGEOM*)sub_mpt);
1886  }
1887  }
1888  }
1889  return mpt;
1890 }
1891 
1892 LWMPOINT*
1893 lwgeom_to_points(const LWGEOM* lwgeom, uint32_t npoints, int32_t seed)
1894 {
1895  switch (lwgeom_get_type(lwgeom))
1896  {
1897  case MULTIPOLYGONTYPE:
1898  return lwmpoly_to_points((LWMPOLY*)lwgeom, npoints, seed);
1899  case POLYGONTYPE:
1900  return lwpoly_to_points((LWPOLY*)lwgeom, npoints, seed);
1901  default:
1902  lwerror("%s: unsupported geometry type '%s'", __func__, lwtype_name(lwgeom_get_type(lwgeom)));
1903  return NULL;
1904  }
1905 }
1906 
1907 LWTIN*
1908 lwtin_from_geos(const GEOSGeometry* geom, uint8_t want3d)
1909 {
1910  int type = GEOSGeomTypeId(geom);
1911  int SRID = GEOSGetSRID(geom);
1912 
1913  /* GEOS's 0 is equivalent to our unknown as for SRID values */
1914  if (SRID == 0) SRID = SRID_UNKNOWN;
1915 
1916  if (want3d && !GEOSHasZ(geom))
1917  {
1918  LWDEBUG(3, "Geometry has no Z, won't provide one");
1919  want3d = 0;
1920  }
1921 
1922  switch (type)
1923  {
1924  LWTRIANGLE** geoms;
1925  uint32_t i, ngeoms;
1926  case GEOS_GEOMETRYCOLLECTION:
1927  LWDEBUG(4, "lwgeom_from_geometry: it's a Collection or Multi");
1928 
1929  ngeoms = GEOSGetNumGeometries(geom);
1930  geoms = NULL;
1931  if (ngeoms)
1932  {
1933  geoms = lwalloc(ngeoms * sizeof *geoms);
1934  if (!geoms)
1935  {
1936  lwerror("lwtin_from_geos: can't allocate geoms");
1937  return NULL;
1938  }
1939  for (i = 0; i < ngeoms; i++)
1940  {
1941  const GEOSGeometry *poly, *ring;
1942  const GEOSCoordSequence* cs;
1943  POINTARRAY* pa;
1944 
1945  poly = GEOSGetGeometryN(geom, i);
1946  ring = GEOSGetExteriorRing(poly);
1947  cs = GEOSGeom_getCoordSeq(ring);
1948  pa = ptarray_from_GEOSCoordSeq(cs, want3d);
1949 
1950  geoms[i] = lwtriangle_construct(SRID, NULL, pa);
1951  }
1952  }
1953  return (LWTIN*)lwcollection_construct(TINTYPE, SRID, NULL, ngeoms, (LWGEOM**)geoms);
1954  case GEOS_POLYGON:
1955  case GEOS_MULTIPOINT:
1956  case GEOS_MULTILINESTRING:
1957  case GEOS_MULTIPOLYGON:
1958  case GEOS_LINESTRING:
1959  case GEOS_LINEARRING:
1960  case GEOS_POINT:
1961  lwerror("lwtin_from_geos: invalid geometry type for tin: %d", type);
1962  break;
1963 
1964  default:
1965  lwerror("GEOS2LWGEOM: unknown geometry type: %d", type);
1966  return NULL;
1967  }
1968 
1969  /* shouldn't get here */
1970  return NULL;
1971 }
1972 /*
1973  * output = 1 for edges, 2 for TIN, 0 for polygons
1974  */
1975 LWGEOM*
1976 lwgeom_delaunay_triangulation(const LWGEOM* geom, double tolerance, int32_t output)
1977 {
1978  LWGEOM* result;
1979  int32_t srid = RESULT_SRID(geom);
1980  uint8_t is3d = FLAGS_GET_Z(geom->flags);
1981  GEOSGeometry *g1, *g3;
1982 
1983  if (output < 0 || output > 2)
1984  {
1985  lwerror("%s: invalid output type specified %d", __func__, output);
1986  return NULL;
1987  }
1988 
1989  if (srid == SRID_INVALID) return NULL;
1990 
1991  initGEOS(lwnotice, lwgeom_geos_error);
1992 
1993  if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL();
1994 
1995  /* if output != 1 we want polys */
1996  g3 = GEOSDelaunayTriangulation(g1, tolerance, output == 1);
1997 
1998  if (!g3) GEOS_FREE_AND_FAIL(g1);
1999  GEOSSetSRID(g3, srid);
2000 
2001  if (output == 2)
2002  {
2003  result = (LWGEOM*)lwtin_from_geos(g3, is3d);
2004  if (!result)
2005  {
2006  GEOS_FREE(g1, g3);
2007  lwerror("%s: cannot convert output geometry", __func__);
2008  return NULL;
2009  }
2010  lwgeom_set_srid(result, srid);
2011  }
2012  else if (!(result = GEOS2LWGEOM(g3, is3d)))
2013  GEOS_FREE_AND_FAIL(g1, g3);
2014 
2015  GEOS_FREE(g1, g3);
2016  return result;
2017 }
2018 
2019 
2020 static GEOSCoordSequence*
2021 lwgeom_get_geos_coordseq_2d(const LWGEOM* g, uint32_t num_points)
2022 {
2023  uint32_t i = 0;
2024  uint8_t num_dims = 2;
2025  LWPOINTITERATOR* it;
2026  GEOSCoordSequence* coords;
2027  POINT4D tmp;
2028 
2029  coords = GEOSCoordSeq_create(num_points, num_dims);
2030  if (!coords) return NULL;
2031 
2032  it = lwpointiterator_create(g);
2033  while (lwpointiterator_next(it, &tmp))
2034  {
2035  if (i >= num_points)
2036  {
2037  lwerror("Incorrect num_points provided to lwgeom_get_geos_coordseq_2d");
2038  GEOSCoordSeq_destroy(coords);
2040  return NULL;
2041  }
2042 
2043 #if POSTGIS_GEOS_VERSION < 30800
2044  if (!GEOSCoordSeq_setX(coords, i, tmp.x) || !GEOSCoordSeq_setY(coords, i, tmp.y))
2045 #else
2046  if (!GEOSCoordSeq_setXY(coords, i, tmp.x, tmp.y))
2047 #endif
2048  {
2049  GEOSCoordSeq_destroy(coords);
2051  return NULL;
2052  }
2053  i++;
2054  }
2056 
2057  return coords;
2058 }
2059 
2060 LWGEOM*
2061 lwgeom_voronoi_diagram(const LWGEOM* g, const GBOX* env, double tolerance, int output_edges)
2062 {
2063  uint32_t num_points = lwgeom_count_vertices(g);
2064  LWGEOM* lwgeom_result;
2065  char is_3d = LW_FALSE;
2066  int32_t srid = lwgeom_get_srid(g);
2067  GEOSCoordSequence* coords;
2068  GEOSGeometry* geos_geom;
2069  GEOSGeometry* geos_env = NULL;
2070  GEOSGeometry* geos_result;
2071 
2072  if (num_points < 2)
2073  {
2075  return lwcollection_as_lwgeom(empty);
2076  }
2077 
2078  initGEOS(lwnotice, lwgeom_geos_error);
2079 
2080  /* Instead of using the standard LWGEOM2GEOS transformer, we read the vertices of the LWGEOM directly and put
2081  * them into a single GEOS CoordinateSeq that can be used to define a LineString. This allows us to process
2082  * geometry types that may not be supported by GEOS, and reduces the memory requirements in cases of many
2083  * geometries with few points (such as LWMPOINT).*/
2084  coords = lwgeom_get_geos_coordseq_2d(g, num_points);
2085  if (!coords) return NULL;
2086 
2087  geos_geom = GEOSGeom_createLineString(coords);
2088  if (!geos_geom)
2089  {
2090  GEOSCoordSeq_destroy(coords);
2091  return NULL;
2092  }
2093 
2094  if (env) geos_env = GBOX2GEOS(env);
2095 
2096  geos_result = GEOSVoronoiDiagram(geos_geom, geos_env, tolerance, output_edges);
2097 
2098  GEOSGeom_destroy(geos_geom);
2099  if (env) GEOSGeom_destroy(geos_env);
2100 
2101  if (!geos_result)
2102  {
2103  lwerror("GEOSVoronoiDiagram: %s", lwgeom_geos_errmsg);
2104  return NULL;
2105  }
2106 
2107  lwgeom_result = GEOS2LWGEOM(geos_result, is_3d);
2108  GEOSGeom_destroy(geos_result);
2109 
2110  lwgeom_set_srid(lwgeom_result, srid);
2111 
2112  return lwgeom_result;
2113 }
2114 
2115 
2116 #if POSTGIS_GEOS_VERSION >= 31100
2117 LWGEOM*
2118 lwgeom_concavehull(const LWGEOM* geom, double ratio, uint32_t allow_holes)
2119 {
2120  LWGEOM* result;
2121  int32_t srid = RESULT_SRID(geom);
2122  uint8_t is3d = FLAGS_GET_Z(geom->flags);
2123  GEOSGeometry *g1, *g3;
2124  int geosGeomType;
2125 
2126  initGEOS(lwnotice, lwgeom_geos_error);
2127 
2128  if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL();
2129 
2130  geosGeomType = GEOSGeomTypeId(g1);
2131  if (geosGeomType == GEOS_POLYGON || geosGeomType == GEOS_MULTIPOLYGON) {
2132  int is_tight = LW_FALSE;
2133  g3 = GEOSConcaveHullOfPolygons(g1, ratio, is_tight, allow_holes);
2134  }
2135  else {
2136  g3 = GEOSConcaveHull(g1, ratio, allow_holes);
2137  }
2138 
2139  if (!g3)
2140  GEOS_FREE_AND_FAIL(g1);
2141 
2142  GEOSSetSRID(g3, srid);
2143 
2144  if (!(result = GEOS2LWGEOM(g3, is3d)))
2145  GEOS_FREE_AND_FAIL(g1, g3);
2146 
2147  GEOS_FREE(g1, g3);
2148  return result;
2149 }
2150 
2151 LWGEOM*
2152 lwgeom_simplify_polygonal(const LWGEOM* geom, double vertex_fraction, uint32_t is_outer)
2153 {
2154  LWGEOM* result;
2155  int32_t srid = RESULT_SRID(geom);
2156  uint8_t is3d = FLAGS_GET_Z(geom->flags);
2157  GEOSGeometry *g1, *g3;
2158 
2159  initGEOS(lwnotice, lwgeom_geos_error);
2160 
2161  if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL();
2162 
2163  g3 = GEOSPolygonHullSimplify(g1, is_outer, vertex_fraction);
2164 
2165  if (!g3)
2166  GEOS_FREE_AND_FAIL(g1);
2167 
2168  GEOSSetSRID(g3, srid);
2169 
2170  if (!(result = GEOS2LWGEOM(g3, is3d)))
2171  GEOS_FREE_AND_FAIL(g1, g3);
2172 
2173  GEOS_FREE(g1, g3);
2174  return result;
2175 }
2176 
2177 LWGEOM*
2178 lwgeom_triangulate_polygon(const LWGEOM* geom)
2179 {
2180  LWGEOM* result;
2181  int32_t srid = RESULT_SRID(geom);
2182  uint8_t is3d = FLAGS_GET_Z(geom->flags);
2183  GEOSGeometry *g1, *g3;
2184 
2185  if (srid == SRID_INVALID) return NULL;
2186 
2187  initGEOS(lwnotice, lwgeom_geos_error);
2188 
2189  if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL();
2190 
2191  /* if output != 1 we want polys */
2192  g3 = GEOSConstrainedDelaunayTriangulation(g1);
2193 
2194  if (!g3) GEOS_FREE_AND_FAIL(g1);
2195  GEOSSetSRID(g3, srid);
2196 
2197  if (!(result = GEOS2LWGEOM(g3, is3d)))
2198  GEOS_FREE_AND_FAIL(g1, g3);
2199 
2200  GEOS_FREE(g1, g3);
2201  return result;
2202 }
2203 
2204 #endif
char result[OUT_DOUBLE_BUFFER_SIZE]
Definition: cu_print.c:267
#define LWGEOM_GEOS_ERRMSG_MAXSIZE
#define GEOS_FREE(...)
LWGEOM * lwgeom_centroid(const LWGEOM *geom)
LWGEOM * lwgeom_intersection_prec(const LWGEOM *geom1, const LWGEOM *geom2, double prec)
LWGEOM * lwgeom_geos_noop(const LWGEOM *geom)
Convert an LWGEOM to a GEOS Geometry and convert back – for debug only.
static LWGEOM * lwcollection_offsetcurve(const LWCOLLECTION *col, double size, int quadsegs, int joinStyle, double mitreLimit)
LWGEOM * lwgeom_linemerge_directed(const LWGEOM *geom, int directed)
static GEOSGeometry * ptarray_to_GEOSLinearRing(const POINTARRAY *pa, uint8_t autofix)
LWGEOM * lwgeom_symdifference(const LWGEOM *geom1, const LWGEOM *geom2)
LWGEOM * lwgeom_unaryunion(const LWGEOM *geom)
LWGEOM * lwgeom_union(const LWGEOM *g1, const LWGEOM *g2)
GEOSGeometry * make_geos_point(double x, double y)
#define AUTOFIX
static LWGEOM * lwline_offsetcurve(const LWLINE *lwline, double size, int quadsegs, int joinStyle, double mitreLimit)
LWGEOM * lwgeom_offsetcurve(const LWGEOM *geom, double size, int quadsegs, int joinStyle, double mitreLimit)
LWGEOM * lwgeom_delaunay_triangulation(const LWGEOM *geom, double tolerance, int32_t output)
Take vertices of a geometry and build a delaunay triangulation on them.
LWGEOM * lwgeom_difference_prec(const LWGEOM *geom1, const LWGEOM *geom2, double prec)
#define RESULT_SRID(...)
LWGEOM * lwgeom_snap(const LWGEOM *geom1, const LWGEOM *geom2, double tolerance)
Snap vertices and segments of a geometry to another using a given tolerance.
LWGEOM * lwgeom_difference(const LWGEOM *geom1, const LWGEOM *geom2)
#define GEOS_FAIL()
int lwgeom_is_simple(const LWGEOM *geom)
LWGEOM * lwgeom_pointonsurface(const LWGEOM *geom)
LWGEOM * lwgeom_sharedpaths(const LWGEOM *geom1, const LWGEOM *geom2)
LWGEOM * lwgeom_symdifference_prec(const LWGEOM *geom1, const LWGEOM *geom2, double prec)
char lwgeom_geos_errmsg[LWGEOM_GEOS_ERRMSG_MAXSIZE]
LWGEOM * lwgeom_voronoi_diagram(const LWGEOM *g, const GBOX *env, double tolerance, int output_edges)
Take vertices of a geometry and build the Voronoi diagram.
LWGEOM * lwgeom_union_prec(const LWGEOM *geom1, const LWGEOM *geom2, double gridSize)
LWGEOM * lwgeom_normalize(const LWGEOM *geom)
static int32_t get_result_srid(size_t count, const char *funcname,...)
#define GEOS_FREE_AND_FAIL_DEBUG(...)
LWGEOM * lwgeom_buildarea(const LWGEOM *geom)
Take a geometry and return an areal geometry (Polygon or MultiPolygon).
#define GEOS_FAIL_DEBUG()
LWGEOM * lwgeom_linemerge(const LWGEOM *geom)
#define GEOS_FREE_AND_FAIL(...)
GEOSGeometry * make_geos_segment(double x1, double y1, double x2, double y2)
static void geos_destroy(size_t count,...)
GEOSGeometry * GBOX2GEOS(const GBOX *box)
const char * lwgeom_geos_version()
Return GEOS version string (not to be freed)
GEOSGeometry * LWGEOM2GEOS(const LWGEOM *lwgeom, uint8_t autofix)
LWGEOM * lwgeom_reduceprecision(const LWGEOM *geom, double gridSize)
LWMPOINT * lwgeom_to_points(const LWGEOM *lwgeom, uint32_t npoints, int32_t seed)
LWGEOM * GEOS2LWGEOM(const GEOSGeometry *geom, uint8_t want3d)
GEOSCoordSeq ptarray_to_GEOSCoordSeq(const POINTARRAY *, uint8_t fix_ring)
LWMPOINT * lwmpoly_to_points(const LWMPOLY *lwmpoly, uint32_t npoints, int32_t seed)
LWGEOM * lwgeom_unaryunion_prec(const LWGEOM *geom, double prec)
POINTARRAY * ptarray_from_GEOSCoordSeq(const GEOSCoordSequence *cs, uint8_t want3d)
LWMPOINT * lwpoly_to_points(const LWPOLY *lwpoly, uint32_t npoints, int32_t seed)
LWGEOM * lwgeom_intersection(const LWGEOM *g1, const LWGEOM *g2)
LWGEOM * lwgeom_clip_by_rect(const LWGEOM *geom1, double x1, double y1, double x2, double y2)
void lwgeom_geos_error(const char *fmt,...)
static GEOSCoordSequence * lwgeom_get_geos_coordseq_2d(const LWGEOM *g, uint32_t num_points)
LWTIN * lwtin_from_geos(const GEOSGeometry *geom, uint8_t want3d)
LWPOINT * lwpoint_construct_empty(int32_t srid, char hasz, char hasm)
Definition: lwpoint.c:151
LWLINE * lwgeom_as_lwline(const LWGEOM *lwgeom)
Definition: lwgeom.c:179
LWGEOM * lwline_as_lwgeom(const LWLINE *obj)
Definition: lwgeom.c:339
#define LW_FALSE
Definition: liblwgeom.h:109
LWGEOM * lwcollection_as_lwgeom(const LWCOLLECTION *obj)
Definition: lwgeom.c:309
#define COLLECTIONTYPE
Definition: liblwgeom.h:123
int32_t lwgeom_get_srid(const LWGEOM *geom)
Return SRID number.
Definition: lwgeom.c:927
LWGEOM * lwgeom_node(const LWGEOM *lwgeom_in)
LWPOINT * lwpoint_make2d(int32_t srid, double x, double y)
Definition: lwpoint.c:163
#define SRID_INVALID
Definition: liblwgeom.h:234
LWPOINTITERATOR * lwpointiterator_create(const LWGEOM *g)
Create a new LWPOINTITERATOR over supplied LWGEOM*.
Definition: lwiterator.c:242
void lwgeom_set_srid(LWGEOM *geom, int32_t srid)
Set the SRID on an LWGEOM For collections, only the parent gets an SRID, all the children get SRID_UN...
Definition: lwgeom.c:1547
void lwgeom_free(LWGEOM *geom)
Definition: lwgeom.c:1155
#define MULTILINETYPE
Definition: liblwgeom.h:121
int lwpointiterator_next(LWPOINTITERATOR *s, POINT4D *p)
Attempts to assign the next point in the iterator to p, and advances the iterator to the next point.
Definition: lwiterator.c:210
#define LINETYPE
Definition: liblwgeom.h:118
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
LWGEOM * lwgeom_stroke(const LWGEOM *geom, uint32_t perQuad)
Convert type with arcs into equivalent linearized type.
Definition: lwstroke.c:871
#define MULTIPOINTTYPE
Definition: liblwgeom.h:120
LWGEOM * lwgeom_clone_deep(const LWGEOM *lwgeom)
Deep clone an LWGEOM, everything is copied.
Definition: lwgeom.c:529
void lwpointiterator_destroy(LWPOINTITERATOR *s)
Free all memory associated with the iterator.
Definition: lwiterator.c:267
POINTARRAY * ptarray_construct(char hasz, char hasm, uint32_t npoints)
Construct an empty pointarray, allocating storage and setting the npoints, but not filling in any inf...
Definition: ptarray.c:51
int lwgeom_has_z(const LWGEOM *geom)
Return LW_TRUE if geometry has Z ordinates.
Definition: lwgeom.c:934
#define POINTTYPE
LWTYPE numbers, used internally by PostGIS.
Definition: liblwgeom.h:117
char * lwgeom_to_ewkt(const LWGEOM *lwgeom)
Return an alloced string.
Definition: lwgeom.c:565
#define FLAGS_GET_Z(flags)
Definition: liblwgeom.h:180
LWLINE * lwline_construct(int32_t srid, GBOX *bbox, POINTARRAY *points)
Definition: lwline.c:42
LWMPOINT * lwmpoint_add_lwpoint(LWMPOINT *mobj, const LWPOINT *obj)
Definition: lwmpoint.c:45
double lwgeom_area(const LWGEOM *geom)
Definition: lwgeom.c:1908
int lwgeom_type_arc(const LWGEOM *geom)
Geometry type is one of the potentially "arc containing" types (circstring, multicurve,...
Definition: lwstroke.c:89
#define TINTYPE
Definition: liblwgeom.h:131
#define MULTIPOLYGONTYPE
Definition: liblwgeom.h:122
uint32_t lwgeom_count_vertices(const LWGEOM *geom)
Count the total number of vertices in any LWGEOM.
Definition: lwgeom.c:1246
int lwgeom_dimension(const LWGEOM *geom)
For an LWGEOM, returns 0 for points, 1 for lines, 2 for polygons, 3 for volume, and the max dimension...
Definition: lwgeom.c:1298
void lwfree(void *mem)
Definition: lwutil.c:242
LWGEOM * lwpoint_as_lwgeom(const LWPOINT *obj)
Definition: lwgeom.c:344
#define FLAGS_NDIMS(flags)
Definition: liblwgeom.h:194
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:119
LWMPOINT * lwmpoint_construct_empty(int32_t srid, char hasz, char hasm)
Definition: lwmpoint.c:39
LWPOINT * lwpoint_construct(int32_t srid, GBOX *bbox, POINTARRAY *point)
Definition: lwpoint.c:129
void lwcollection_release(LWCOLLECTION *lwcollection)
Definition: lwcollection.c:36
LWCOLLECTION * lwcollection_construct_empty(uint8_t type, int32_t srid, char hasz, char hasm)
Definition: lwcollection.c:92
const char * lwtype_name(uint8_t type)
Return the type name string associated with a type number (e.g.
Definition: lwutil.c:216
#define FLAGS_GET_M(flags)
Definition: liblwgeom.h:181
void lwcollection_free(LWCOLLECTION *col)
Definition: lwcollection.c:357
int lwgeom_calculate_gbox(const LWGEOM *lwgeom, GBOX *gbox)
Calculate bounding box of a geometry, automatically taking into account whether it is cartesian or ge...
Definition: lwgeom.c:755
LWCOLLECTION * lwcollection_concat_in_place(LWCOLLECTION *col1, const LWCOLLECTION *col2)
Appends all geometries from col2 to col1 in place.
Definition: lwcollection.c:241
#define TRIANGLETYPE
Definition: liblwgeom.h:130
LWCOLLECTION * lwcollection_add_lwgeom(LWCOLLECTION *col, const LWGEOM *geom)
Appends geom to the collection managed by col.
Definition: lwcollection.c:188
void * lwalloc(size_t size)
Definition: lwutil.c:227
LWCOLLECTION * lwcollection_construct(uint8_t type, int32_t srid, GBOX *bbox, uint32_t ngeoms, LWGEOM **geoms)
Definition: lwcollection.c:42
int ptarray_is_closed_2d(const POINTARRAY *pa)
Definition: ptarray.c:701
LWCOLLECTION * lwgeom_as_lwcollection(const LWGEOM *lwgeom)
Definition: lwgeom.c:233
#define LW_TRUE
Return types for functions with status returns.
Definition: liblwgeom.h:108
void lwgeom_release(LWGEOM *lwgeom)
Free the containing LWGEOM and the associated BOX.
Definition: lwgeom.c:468
#define SRID_UNKNOWN
Unknown SRID value.
Definition: liblwgeom.h:230
int lwgeom_has_m(const LWGEOM *geom)
Return LW_TRUE if geometry has M ordinates.
Definition: lwgeom.c:941
LWPOLY * lwpoly_construct_empty(int32_t srid, char hasz, char hasm)
Definition: lwpoly.c:161
LWPOLY * lwpoly_construct(int32_t srid, GBOX *bbox, uint32_t nrings, POINTARRAY **points)
Definition: lwpoly.c:43
void ptarray_set_point4d(POINTARRAY *pa, uint32_t n, const POINT4D *p4d)
Definition: lwgeom_api.c:370
LWLINE * lwline_construct_empty(int32_t srid, char hasz, char hasm)
Definition: lwline.c:55
LWTRIANGLE * lwtriangle_construct(int32_t srid, GBOX *bbox, POINTARRAY *points)
Definition: lwtriangle.c:40
This library is the generic geometry handling section of PostGIS.
#define LW_ON_INTERRUPT(x)
#define FP_MIN(A, B)
double lwpoly_area(const LWPOLY *poly)
Find the area of the outer ring - sum (area of inner rings).
Definition: lwpoly.c:434
#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 const POINT2D * getPoint2d_cp(const POINTARRAY *pa, uint32_t n)
Returns a POINT2D pointer into the POINTARRAY serialized_ptlist, suitable for reading from.
Definition: lwinline.h:101
static const POINT3D * getPoint3d_cp(const POINTARRAY *pa, uint32_t n)
Returns a POINT3D pointer into the POINTARRAY serialized_ptlist, suitable for reading from.
Definition: lwinline.h:113
static uint32_t lwgeom_get_type(const LWGEOM *geom)
Return LWTYPE number.
Definition: lwinline.h:145
static uint8_t * getPoint_internal(const POINTARRAY *pa, uint32_t n)
Definition: lwinline.h:77
static int lwgeom_is_empty(const LWGEOM *geom)
Return true or false depending on whether a geometry is an "empty" geometry (no vertices members)
Definition: lwinline.h:203
void lwrandom_set_seed(int32_t seed)
Definition: lwrandom.c:48
double lwrandom_uniform(void)
Definition: lwrandom.c:94
int count
Definition: genraster.py:57
type
Definition: ovdump.py:42
def fmt
Definition: pixval.py:93
Datum contains(PG_FUNCTION_ARGS)
double ymax
Definition: liblwgeom.h:372
double xmax
Definition: liblwgeom.h:370
double ymin
Definition: liblwgeom.h:371
double xmin
Definition: liblwgeom.h:369
lwflags_t flags
Definition: liblwgeom.h:592
uint32_t ngeoms
Definition: liblwgeom.h:595
LWGEOM ** geoms
Definition: liblwgeom.h:590
uint8_t type
Definition: liblwgeom.h:477
int32_t srid
Definition: liblwgeom.h:475
lwflags_t flags
Definition: liblwgeom.h:476
POINTARRAY * points
Definition: liblwgeom.h:498
uint32_t ngeoms
Definition: liblwgeom.h:553
LWPOINT ** geoms
Definition: liblwgeom.h:548
uint32_t ngeoms
Definition: liblwgeom.h:581
LWPOLY ** geoms
Definition: liblwgeom.h:576
POINTARRAY * point
Definition: liblwgeom.h:486
POINTARRAY ** rings
Definition: liblwgeom.h:534
uint32_t nrings
Definition: liblwgeom.h:539
GBOX * bbox
Definition: liblwgeom.h:533
POINTARRAY * points
Definition: liblwgeom.h:510
double y
Definition: liblwgeom.h:405
double x
Definition: liblwgeom.h:405
double z
Definition: liblwgeom.h:417
double x
Definition: liblwgeom.h:417
double y
Definition: liblwgeom.h:417
double x
Definition: liblwgeom.h:429
double z
Definition: liblwgeom.h:429
double y
Definition: liblwgeom.h:429
lwflags_t flags
Definition: liblwgeom.h:446
uint32_t npoints
Definition: liblwgeom.h:442
uint8_t * serialized_pointlist
Definition: liblwgeom.h:449