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