1 /**********************************************************************
2  *
3  * rttopo - topology library
4  * http://git.osgeo.org/gitea/rttopo/librttopo
5  *
6  * rttopo 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  * rttopo 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 rttopo.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  **********************************************************************
20  *
21  * Implementation of Trevisani-Peri snap algoritm
22  *
23  * See
24  * https://git.osgeo.org/gitea/rttopo/librttopo/wiki/SnapToTopo-algorithm
25  *
26  **********************************************************************
27  *
28  * Copyright (C) 2016 Sandro Santilli <strk@kbt.io>
29  *
30  **********************************************************************/
31 
32 #include "rttopo_config.h"
33 
34 /*#define RTGEOM_DEBUG_LEVEL 4*/
35 #include "rtgeom_log.h"
36 
37 #include "librttopo_geom.h"
38 #include "librttopo_internal.h"
39 #include "librttopo_geom_internal.h"
40 #include "measures.h"
41 #include "rtgeom_geos.h"
42 
43 /*
44  * Reference vertex
45  *
46  * It is the vertex of a topology edge
47  * which is within snap tolerance distance from
48  * a segment of the input geometry.
49  *
50  * We store the input geometry segment and the distance
51  * (both needed to compute the distance) within the structure.
52  *
53  */
54 typedef struct {
55   RTPOINT2D pt;
56   /* Closest segment in input pointarray (0-based index) */
57   int segno;
58   double dist;
59 } RTT_SNAPV;
60 
61 /* An array of RTT_SNAPV structs */
62 typedef struct {
63   RTT_SNAPV *pts;
64   int size;
65   int capacity;
66 } RTT_SNAPV_ARRAY;
67 
68 #define RTT_SNAPV_ARRAY_INIT(c, a) { \
69   (a)->size = 0; \
70   (a)->capacity = 1; \
71   (a)->pts = rtalloc((c), sizeof(RTT_SNAPV) * (a)->capacity); \
72 }
73 
74 #define RTT_SNAPV_ARRAY_CLEAN(c, a) { \
75   rtfree((c), (a)->pts); \
76   (a)->pts = NULL; \
77   (a)->size = 0; \
78   (a)->capacity = 0; \
79 }
80 
81 #define RTT_SNAPV_ARRAY_PUSH(c, a, r) { \
82   if ( (a)->size + 1 > (a)->capacity ) { \
83     (a)->capacity *= 2; \
84     (a)->pts = rtrealloc((c), (a)->pts, sizeof(RTT_SNAPV) * (a)->capacity); \
85   } \
86   (a)->pts[(a)->size++] = (r); \
87 }
88 
89 typedef struct
90 {
91 
92   /*
93    * Input parameters / configuration
94    */
95   const RTT_TOPOLOGY *topo;
96   double tolerance_snap;
97   double tolerance_removal;
98   int iterate;
99 
100   /*
101    * Extent of the geometry being snapped,
102    * will be updated as needed as snapping occurs
103    */
104   RTGBOX workext;
105   RTGBOX expanded_workext;
106 
107   /*
108    * Edges within workext,
109    * will be updated as needed as workext extends
110    * (maybe should be put in an STRtree)
111    */
112   RTT_ISO_EDGE *workedges;
113   int num_workedges;
114 
115 } rtgeom_tpsnap_state;
116 
117 /*
118  * Write number of edges in *num_edges, -1 on error.
119  * @return edges, or NULL if none-or-error (look *num_edges to tell)
120  */
121 static const RTT_ISO_EDGE *
rtgeom_tpsnap_state_get_edges(rtgeom_tpsnap_state * state,int * num_edges)122 rtgeom_tpsnap_state_get_edges(rtgeom_tpsnap_state *state, int *num_edges)
123 {
124   if ( ! state->workedges ) {
125     state->workedges = rtt_be_getEdgeWithinBox2D(state->topo,
126               &state->expanded_workext,
127               &state->num_workedges,
128               RTT_COL_EDGE_ALL, 0);
129   }
130 
131   *num_edges = state->num_workedges;
132   return state->workedges;
133 }
134 
135 /*
136  * Expand working extent to include new point.
137  * Resets working edges if new point expands the last used bounding box.
138  */
139 static void
rtgeom_tpsnap_state_expand_workext_to_include(rtgeom_tpsnap_state * state,RTPOINT2D * pt)140 rtgeom_tpsnap_state_expand_workext_to_include(rtgeom_tpsnap_state *state,
141     RTPOINT2D *pt)
142 {
143   const RTCTX *ctx = state->topo->be_iface->ctx;
144   POINT3D p3d;
145 
146   /* Nothing to do if the box already contains the point */
147   if ( gbox_contains_point2d(ctx, &state->workext, pt) ) return;
148 
149   p3d.x = pt->x;
150   p3d.y = pt->y;
151   p3d.z = 0.0;
152 
153   gbox_merge_point3d(ctx, &p3d, &state->workext);
154   state->expanded_workext = state->workext;
155   gbox_expand(ctx, &(state->expanded_workext), state->tolerance_snap);
156 
157   /* Reset workedges */
158   if ( state->workedges ) {
159     rtt_release_edges(state->topo->be_iface->ctx,
160                       state->workedges, state->num_workedges);
161     state->workedges = NULL;
162   }
163 }
164 
165 static void
rtgeom_tpsnap_state_destroy(rtgeom_tpsnap_state * state)166 rtgeom_tpsnap_state_destroy(rtgeom_tpsnap_state *state)
167 {
168   if ( state->workedges ) {
169     rtt_release_edges(state->topo->be_iface->ctx,
170                       state->workedges, state->num_workedges);
171   }
172 }
173 
174 /*
175  * Find closest segment of pa to a given point
176  *
177  * @return -1 on error, 0 on success
178  */
179 static int
_rt_find_closest_segment(const RTCTX * ctx,RTPOINT2D * pt,RTPOINTARRAY * pa,int * segno,double * dist)180 _rt_find_closest_segment(const RTCTX *ctx, RTPOINT2D *pt, RTPOINTARRAY *pa,
181     int *segno, double *dist)
182 {
183   int j;
184   RTPOINT2D s0, s1;
185   DISTPTS dl;
186 
187   *segno = -1;
188   *dist = FLT_MAX;
189 
190   if ( pa->npoints < 2 ) return 0;
191 
192   rt_dist2d_distpts_init(ctx, &dl, DIST_MIN);
193 
194   /* Find closest segment */
195   rt_getPoint2d_p(ctx, pa, 0, &s0);
196   for (j=0; j<pa->npoints-1; ++j)
197   {
198     rt_getPoint2d_p(ctx, pa, j+1, &s1);
199 
200     if ( rt_dist2d_pt_seg(ctx, pt, &s0, &s1, &dl) == RT_FALSE )
201     {
202       rterror(ctx, "rt_dist2d_pt_seg failed in _rt_find_closest_segment");
203       return -1;
204     }
205 
206     if ( dl.distance < *dist )
207     {
208       /* Segment is closest so far */
209       *segno = j;
210       *dist = dl.distance;
211     }
212 
213     s0 = s1;
214   }
215 
216   return 0;
217 }
218 
219 /*
220  * Extract from edge all vertices where distance from pa <= tolerance_snap
221  *
222  * @return -1 on error, 0 on success
223  */
224 static int
_rt_extract_vertices_within_dist(rtgeom_tpsnap_state * state,RTT_SNAPV_ARRAY * vset,RTLINE * edge,RTPOINTARRAY * pa)225 _rt_extract_vertices_within_dist(rtgeom_tpsnap_state *state,
226     RTT_SNAPV_ARRAY *vset, RTLINE *edge, RTPOINTARRAY *pa)
227 {
228   int i;
229   RTPOINTARRAY *epa = edge->points; /* edge's point array */
230   const RTT_TOPOLOGY *topo = state->topo;
231   const RTCTX *ctx = topo->be_iface->ctx;
232 
233   RTT_SNAPV vert;
234   for (i=0; i<epa->npoints; ++i)
235   {
236     int ret;
237 
238     rt_getPoint2d_p(ctx, edge->points, i, &(vert.pt));
239 
240     /* skip if not covered by expanded_workext */
241     if ( vert.pt.x < state->expanded_workext.xmin ||
242          vert.pt.x > state->expanded_workext.xmax ||
243          vert.pt.y < state->expanded_workext.ymin ||
244          vert.pt.y > state->expanded_workext.ymax )
245     {
246       RTDEBUGF(ctx, 3, "skip point %g,%g outside expanded workext %g,%g,%g,%g", vert.pt.x, vert.pt.y, state->expanded_workext.xmin,state->expanded_workext.ymin,state->expanded_workext.xmax,state->expanded_workext.ymax);
247       continue;
248     }
249 
250     ret = _rt_find_closest_segment(ctx, &(vert.pt), pa, &vert.segno, &vert.dist);
251     if ( ret == -1 ) return -1;
252 
253     if ( vert.dist <= state->tolerance_snap )
254     {
255       /* push vert to array */
256       RTT_SNAPV_ARRAY_PUSH(ctx, vset, vert);
257     }
258 
259   }
260 
261   return 0;
262 }
263 
264 /*
265  * Find all topology edge vertices where distance from
266  * given pointarray <= tolerance_snap
267  *
268  * @return -1 on error, 0 on success
269  */
270 static int
_rt_find_vertices_within_dist(RTT_SNAPV_ARRAY * vset,RTPOINTARRAY * pa,rtgeom_tpsnap_state * state)271 _rt_find_vertices_within_dist(
272       RTT_SNAPV_ARRAY *vset, RTPOINTARRAY *pa,
273       rtgeom_tpsnap_state *state)
274 {
275   int num_edges;
276   const RTT_ISO_EDGE *edges;
277   const RTT_TOPOLOGY *topo = state->topo;
278   const RTCTX *ctx = topo->be_iface->ctx;
279   int i;
280 
281   edges = rtgeom_tpsnap_state_get_edges(state, &num_edges);
282   if ( num_edges == -1 ) {
283     rterror(ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
284     return -1;
285   }
286 
287   for (i=0; i<num_edges; ++i)
288   {
289     int ret;
290     ret = _rt_extract_vertices_within_dist(state, vset, edges[i].geom, pa);
291     if ( ret < 0 ) return ret;
292   }
293 
294   return 0;
295 }
296 
297 static int
compare_snapv(const void * si1,const void * si2)298 compare_snapv(const void *si1, const void *si2)
299 {
300   RTT_SNAPV *a = (RTT_SNAPV *)si1;
301   RTT_SNAPV *b = (RTT_SNAPV *)si2;
302 
303   if ( a->dist < b->dist )
304     return -1;
305   else if ( a->dist > b->dist )
306     return 1;
307 
308   if ( a->pt.x < b->pt.x )
309     return -1;
310   else if ( a->pt.x > b->pt.x )
311     return 1;
312 
313   if ( a->pt.y < b->pt.y )
314     return -1;
315   else if ( a->pt.y > b->pt.y )
316     return 1;
317 
318   return 0;
319 }
320 
321 /*
322  * @return 0 on success, -1 on error
323  */
324 typedef int (*rtptarray_visitor)(const RTCTX *ctx, RTPOINTARRAY *pa, void *userdata);
325 
326 /*
327  * Pass each PTARRAY defining linear components of RTGEOM to the given
328  * visitor function
329  *
330  * This is a mutating visit, where pointarrays are passed as non-const
331  *
332  * Only (multi)linestring and (multi)polygon will be filtered, with
333  * other components simply left unvisited.
334  *
335  * @return 0 on success, -1 on error (if visitor function ever
336  *         returned an error)
337  *
338  * To be exported if useful
339  */
340 static int
rtgeom_visit_lines(const RTCTX * ctx,RTGEOM * rtgeom,rtptarray_visitor visitor,void * userdata)341 rtgeom_visit_lines(const RTCTX *ctx, RTGEOM *rtgeom,
342                    rtptarray_visitor visitor, void *userdata)
343 {
344   int i;
345   int ret;
346   RTCOLLECTION *coll;
347   RTPOLY *poly;
348   RTLINE *line;
349 
350   switch (rtgeom->type)
351   {
352   case RTPOLYGONTYPE:
353     poly = (RTPOLY*)rtgeom;
354     for (i=0; i<poly->nrings; ++i) {
355       ret = visitor(ctx, poly->rings[i], userdata);
356       if ( ret != 0 ) return ret;
357     }
358     break;
359 
360   case RTLINETYPE:
361     line = (RTLINE*)rtgeom;
362     return visitor(ctx, line->points, userdata);
363 
364   case RTMULTILINETYPE:
365   case RTMULTIPOLYGONTYPE:
366   case RTCOLLECTIONTYPE:
367     coll = (RTCOLLECTION *)rtgeom;
368     for (i=0; i<coll->ngeoms; i++) {
369       ret = rtgeom_visit_lines(ctx, coll->geoms[i], visitor, userdata);
370       if ( ret != 0 ) return ret;
371     }
372     break;
373   }
374 
375   return 0;
376 }
377 
378 /*
379  * Vertex removal phase
380  *
381  * Remove internal vertices of `pa` that are within state.tolerance_snap
382  * distance from edges of state.topo topology.
383  *
384  * @return -1 on error, number of points removed on success
385  */
386 static int
_rtgeom_tpsnap_ptarray_remove(const RTCTX * ctx,RTPOINTARRAY * pa,rtgeom_tpsnap_state * state)387 _rtgeom_tpsnap_ptarray_remove(const RTCTX *ctx, RTPOINTARRAY *pa,
388                   rtgeom_tpsnap_state *state)
389 {
390   int num_edges, i, j, ret;
391   const RTT_ISO_EDGE *edges;
392   const RTT_TOPOLOGY *topo = state->topo;
393   int removed = 0;
394 
395   /* Let *Eset* be the set of edges of *Topo-ref*
396    *             with distance from *Gcomp* <= *TSsnap*
397    */
398   edges = rtgeom_tpsnap_state_get_edges(state, &num_edges);
399   if ( num_edges == -1 ) {
400     rterror(ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
401     return -1;
402   }
403 
404   RTDEBUG(ctx, 1, "vertices removal phase starts");
405 
406   /* For each non-endpoint vertex *V* of *Gcomp* */
407   for (i=1; i<pa->npoints-1; ++i)
408   {
409     RTPOINT2D V;
410     RTLINE *closest_segment_edge = NULL;
411     int closest_segment_number;
412     double closest_segment_distance = state->tolerance_removal+1;
413 
414     rt_getPoint2d_p(ctx, pa, i, &V);
415 
416     RTDEBUGF(ctx, 2, "Analyzing internal vertex POINT(%.15g %.15g)", V.x, V.y);
417 
418     /* Find closest edge segment */
419     for (j=0; j<num_edges; ++j)
420     {
421       RTLINE *E = edges[j].geom;
422       int segno;
423       double dist;
424 
425       ret = _rt_find_closest_segment(ctx, &V, E->points, &segno, &dist);
426       if ( ret < 0 ) return ret; /* error */
427 
428       /* Edge is too far */
429       if ( dist > state->tolerance_removal ) {
430         RTDEBUGF(ctx, 2, " Vertex is too far (%g) from edge %d", dist, edges[j].edge_id);
431         continue;
432       }
433 
434       RTDEBUGF(ctx, 2, " Vertex within distance from segment %d of edge %d",
435         segno, edges[j].edge_id);
436 
437       if ( dist < closest_segment_distance )
438       {
439         closest_segment_edge = E;
440         closest_segment_number = segno;
441         closest_segment_distance = dist;
442       }
443     }
444 
445     if ( closest_segment_edge )
446     {{
447       RTPOINT4D V4d, Ep1, Ep2, proj;
448       RTPOINTARRAY *epa = closest_segment_edge->points;
449 
450       /* Let *Proj* be the closest point in *closest_segment_edge* to *V* */
451       V4d.x = V.x; V4d.y = V.y; V4d.m = V4d.z = 0.0;
452       rt_getPoint4d_p(ctx, epa, closest_segment_number, &Ep1);
453       rt_getPoint4d_p(ctx, epa, closest_segment_number+1, &Ep2);
454       closest_point_on_segment(ctx, &V4d, &Ep1, &Ep2, &proj);
455 
456       RTDEBUGF(ctx, 2, " Closest point on edge segment LINESTRING(%.15g %.15g, %.15g %.15g) is POINT(%.15g %.15g)",
457         Ep1.x, Ep1.y, Ep2.x, Ep2.y, proj.x, proj.y);
458 
459       /* Closest point here matches segment endpoint */
460       if ( p4d_same(ctx, &proj, &Ep1) || p4d_same(ctx, &proj, &Ep2) ) {
461         RTDEBUG(ctx, 2, " Closest point on edge matches segment endpoint");
462         continue;
463       }
464 
465       /* Remove vertex *V* from *Gcomp* */
466       RTDEBUGF(ctx, 1, " Removing internal point POINT(%.14g %.15g)",
467         V.x, V.y);
468       ret = ptarray_remove_point(ctx, pa, i);
469       if ( ret == RT_FAILURE ) return -1;
470       /* rewind i */
471       --i;
472       /* increment removed count */
473       ++removed;
474     }}
475   }
476 
477   RTDEBUGF(ctx, 1, "vertices removal phase ended (%d removed)", removed);
478 
479   return removed;
480 }
481 
482 /* Return NULL on error, or a GEOSGeometry on success */
483 static GEOSGeometry *
_rt_segment_to_geosgeom(const RTCTX * ctx,RTPOINT4D * p1,RTPOINT4D * p2)484 _rt_segment_to_geosgeom(const RTCTX *ctx, RTPOINT4D *p1, RTPOINT4D *p2)
485 {
486   RTPOINTARRAY *pa = ptarray_construct(ctx, 0, 0, 2);
487   RTLINE *line;
488   GEOSGeometry *ret;
489   ptarray_set_point4d(ctx, pa, 0, p1);
490   ptarray_set_point4d(ctx, pa, 1, p2);
491   line = rtline_construct(ctx, 0, NULL, pa);
492   ret = RTGEOM2GEOS(ctx, rtline_as_rtgeom(ctx, line), 0);
493   rtline_free(ctx, line);
494   return ret;
495 }
496 
497 /*
498  *
499  * @return -1 on error, 1 if covered, 0 if not covered
500  */
501 static int
_rt_segment_covered(rtgeom_tpsnap_state * state,RTPOINT4D * p1,RTPOINT4D * p2)502 _rt_segment_covered(rtgeom_tpsnap_state *state,
503     RTPOINT4D *p1, RTPOINT4D *p2)
504 {
505   const RTT_TOPOLOGY *topo = state->topo;
506   const RTCTX *ctx = topo->be_iface->ctx;
507   int num_edges, i;
508   const RTT_ISO_EDGE *edges;
509   GEOSGeometry *sg;
510 
511   edges = rtgeom_tpsnap_state_get_edges(state, &num_edges);
512   if ( num_edges == -1 ) {
513     rterror(ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
514     return -1;
515   }
516 
517   /* OPTIMIZE: use prepared geometries */
518   /* OPTIMIZE: cache cover state of segments */
519 
520   sg = _rt_segment_to_geosgeom(ctx, p1, p2);
521   for (i=0; i<num_edges; ++i)
522   {
523     RTGEOM *eg = rtline_as_rtgeom(ctx, edges[i].geom);
524     GEOSGeometry *geg = RTGEOM2GEOS(ctx, eg, 0);
525     int covers = GEOSCovers_r(ctx->gctx, geg, sg);
526     GEOSGeom_destroy_r(ctx->gctx, geg);
527     if (covers == 2) {
528       GEOSGeom_destroy_r(ctx->gctx, sg);
529       rterror(ctx, "Covers error: %s", rtgeom_get_last_geos_error(ctx));
530       return -1;
531     }
532     if ( covers ) {
533       GEOSGeom_destroy_r(ctx->gctx, sg);
534       return 1;
535     }
536   }
537   GEOSGeom_destroy_r(ctx->gctx, sg);
538 
539   return 0;
540 }
541 
542 /*
543   Let *Point.Proj* be the closest point in *Gcomp* to the point
544   Let *Point.InSeg* be the segment of *Gcomp* containing *Point.Proj*'
545   IF *Point.InSeg* is NOT COVERED BY *Topo-ref* edges:
546       IF *Point.Proj* is NOT cohincident with a vertex of *Gcomp*:
547          Insert *Point* after the first vertex of *Point.InSeg*
548 
549 @return 0 if no valid snap was found, <0 on error, >0 if snapped
550 
551 */
552 static int
_rt_snap_to_valid_vertex(const RTCTX * ctx,RTPOINTARRAY * pa,const RTT_SNAPV * v,rtgeom_tpsnap_state * state)553 _rt_snap_to_valid_vertex(const RTCTX *ctx, RTPOINTARRAY *pa,
554   const RTT_SNAPV *v, rtgeom_tpsnap_state *state)
555 {
556   int ret;
557   RTPOINT4D p, sp1, sp2, proj;
558 
559   p.x = v->pt.x; p.y = v->pt.y; p.m = p.z = 0.0;
560   rt_getPoint4d_p(ctx, pa, v->segno, &sp1);
561   rt_getPoint4d_p(ctx, pa, v->segno+1, &sp2);
562 
563   RTDEBUGF(ctx, 2, "Analyzing snap vertex POINT(%.15g %.15g)", p.x, p.y);
564   RTDEBUGF(ctx, 2, " Closest segment %d is LINESTRING(%.15g %.15g, %.15g %.15g)",
565     v->segno, sp1.x, sp1.y, sp2.x, sp2.y);
566 
567   closest_point_on_segment(ctx, &p, &sp1, &sp2, &proj);
568 
569   RTDEBUGF(ctx, 2, " Closest point on segment is POINT(%.15g %.15g)",
570     proj.x, proj.y);
571 
572 
573   /* Check if closest point matches segment endpoint (could be cached) */
574   if ( p4d_same(ctx, &proj, &sp1) || p4d_same(ctx, &proj, &sp2) )
575   {
576     RTDEBUG(ctx, 2, " Closest point matches a segment's endpoint");
577     return 0;
578   }
579 
580   /* Skip if closest segment is covered by topo-ref */
581   ret = _rt_segment_covered(state, &sp1, &sp2);
582   if ( ret == -1 ) return -1;
583   if ( ret == 1 )
584   {
585     RTDEBUG(ctx, 2, " Closest segment is covered by topo edges");
586     /* it is covered */
587     return 0;
588   }
589 
590   /* Snap ! */
591   RTDEBUGF(ctx, 2, "Snapping input segment %d to POINT(%.15g %.15g)",
592     v->segno, p.x, p.y);
593   ret = ptarray_insert_point(ctx, pa, &p, v->segno+1);
594   if ( ret == RT_FAILURE ) return -1;
595 
596   return 1;
597 
598 }
599 
600 /* @return 0 if no valid snap was found, <0 on error, >0 if snapped */
601 static int
_rt_snap_to_first_valid_vertex(const RTCTX * ctx,RTPOINTARRAY * pa,RTT_SNAPV_ARRAY * vset,rtgeom_tpsnap_state * state)602 _rt_snap_to_first_valid_vertex(const RTCTX *ctx, RTPOINTARRAY *pa,
603   RTT_SNAPV_ARRAY *vset, rtgeom_tpsnap_state *state)
604 {
605   int foundSnap = 0;
606   int i;
607 
608   for (i=0; i<vset->size; ++i)
609   {
610     RTT_SNAPV *v = &(vset->pts[i]);
611     foundSnap = _rt_snap_to_valid_vertex(ctx, pa, v, state);
612     if ( foundSnap ) {
613       if ( foundSnap < 0 ) {
614         RTDEBUGF(ctx, 1, "vertex %d/%d triggered an error while snapping",
615           i, vset->size);
616         return -1;
617       }
618       RTDEBUGF(ctx, 1, "vertex %d/%d was a valid snap",
619         i, vset->size);
620       break;
621     }
622   }
623 
624   return foundSnap;
625 }
626 
627 /*
628  * Vertex addition phase
629  *
630  * @return 0 on success, -1 on error.
631  *
632  */
633 int
_rtgeom_tpsnap_ptarray_add(const RTCTX * ctx,RTPOINTARRAY * pa,rtgeom_tpsnap_state * state)634 _rtgeom_tpsnap_ptarray_add(const RTCTX *ctx, RTPOINTARRAY *pa,
635                   rtgeom_tpsnap_state *state)
636 {
637   int ret;
638   int lookingForSnap = 1;
639 
640   RTDEBUG(ctx, 1, "vertices addition phase starts");
641   while (lookingForSnap)
642   {
643     int foundSnap;
644     RTT_SNAPV_ARRAY vset;
645 
646     lookingForSnap = 0;
647     RTT_SNAPV_ARRAY_INIT(ctx, &vset);
648 
649     ret = _rt_find_vertices_within_dist(&vset, pa, state);
650     if ( ret < 0 ) {
651       RTT_SNAPV_ARRAY_CLEAN(ctx, &vset);
652       return -1;
653     }
654     RTDEBUGF(ctx, 1, "vertices within dist: %d", vset.size);
655     if ( vset.size < 1 ) {
656       RTT_SNAPV_ARRAY_CLEAN(ctx, &vset);
657       break;
658     }
659 
660     qsort(vset.pts, vset.size, sizeof(RTT_SNAPV), compare_snapv);
661 
662     foundSnap = _rt_snap_to_first_valid_vertex(ctx, pa, &vset, state);
663     RTDEBUGF(ctx, 1, "foundSnap: %d", foundSnap);
664 
665     RTT_SNAPV_ARRAY_CLEAN(ctx, &vset);
666 
667     if ( foundSnap < 0 ) return foundSnap; /* error */
668     if ( foundSnap && state->iterate ) {
669       lookingForSnap = 1;
670     }
671   }
672   RTDEBUG(ctx, 1, "vertices addition phase ends");
673 
674   return 0;
675 }
676 
677 /*
678  * Process a single pointarray with the snap algorithm
679  *
680  * @return 0 on success, -1 on error.
681  */
682 int
_rtgeom_tpsnap_ptarray(const RTCTX * ctx,RTPOINTARRAY * pa,void * udata)683 _rtgeom_tpsnap_ptarray(const RTCTX *ctx, RTPOINTARRAY *pa,
684                   void *udata)
685 {
686   int ret;
687   rtgeom_tpsnap_state *state = udata;
688 
689   /* Set work extent to that of the POINTARRAY bounding box */
690   ptarray_calculate_gbox_cartesian(ctx, pa, &(state->workext));
691   state->expanded_workext = state->workext;
692   gbox_expand(ctx, &(state->expanded_workext), state->tolerance_snap);
693 
694   RTDEBUGF(ctx, 1, "Snapping pointarray with %d points", pa->npoints);
695 
696   do {
697     ret = _rtgeom_tpsnap_ptarray_add(ctx, pa, state);
698     if ( ret == -1 ) return -1;
699 
700     if ( state->tolerance_removal >= 0 )
701     {
702       ret = _rtgeom_tpsnap_ptarray_remove(ctx, pa, state);
703       if ( ret == -1 ) return -1;
704     }
705   } while (ret && state->iterate);
706 
707   RTDEBUGF(ctx, 1, "Snapped pointarray has %d points", pa->npoints);
708 
709   return 0;
710 
711 }
712 
713 
714 /* public, exported */
715 RTGEOM *
rtt_tpsnap(RTT_TOPOLOGY * topo,const RTGEOM * gin,double tolerance_snap,double tolerance_removal,int iterate)716 rtt_tpsnap(RTT_TOPOLOGY *topo, const RTGEOM *gin,
717                          double tolerance_snap,
718                          double tolerance_removal,
719                          int iterate)
720 {
721   rtgeom_tpsnap_state state;
722   const RTCTX *ctx = topo->be_iface->ctx;
723   RTGEOM *gtmp = rtgeom_clone_deep(ctx, gin);
724   int ret;
725 
726   RTDEBUGF(ctx, 1, "snapping: tol %g, iterate %d, remove %d",
727     tolerance_snap, iterate, remove_vertices);
728 
729   state.topo = topo;
730   state.tolerance_snap = tolerance_snap;
731   state.tolerance_removal = tolerance_removal;
732   state.iterate = iterate;
733   state.workedges = NULL;
734 
735   rtgeom_geos_ensure_init(ctx);
736 
737   ret = rtgeom_visit_lines(ctx, gtmp, _rtgeom_tpsnap_ptarray, &state);
738 
739   rtgeom_tpsnap_state_destroy(&state);
740 
741   if ( ret ) {
742     rtgeom_free(ctx, gtmp);
743     return NULL;
744   }
745 
746   return gtmp;
747 }
748