[postgis-tickets] r14550 - Rewrite code to split a line by a (multi)point to improve robustness
Sandro Santilli
strk at keybit.net
Tue Jan 5 08:39:56 PST 2016
Author: strk
Date: 2016-01-05 08:39:55 -0800 (Tue, 05 Jan 2016)
New Revision: 14550
Modified:
trunk/liblwgeom/cunit/cu_split.c
trunk/liblwgeom/lwgeom_geos_split.c
trunk/topology/test/regress/st_modedgesplit.sql
trunk/topology/test/regress/st_modedgesplit_expected
Log:
Rewrite code to split a line by a (multi)point to improve robustness
Fixes #3401 including unit and regress test for it.
Modified: trunk/liblwgeom/cunit/cu_split.c
===================================================================
--- trunk/liblwgeom/cunit/cu_split.c 2016-01-05 16:27:58 UTC (rev 14549)
+++ trunk/liblwgeom/cunit/cu_split.c 2016-01-05 16:39:55 UTC (rev 14550)
@@ -246,6 +246,32 @@
lwgeom_free(ret);
lwgeom_free(geom);
lwgeom_free(blade);
+
+ /* See #3401 -- robustness issue */
+ geom = lwgeom_from_wkt("LINESTRING(-180 0,0 0)", LW_PARSER_CHECK_NONE);
+ CU_ASSERT(geom != NULL);
+ blade = lwgeom_from_wkt("POINT(-20 0)", LW_PARSER_CHECK_NONE);
+ ret = lwgeom_split(geom, blade);
+ CU_ASSERT(ret != NULL);
+ {
+ LWCOLLECTION *split = lwgeom_as_lwcollection(ret);
+ LWLINE *l1, *l2;
+ POINT2D pt;
+ CU_ASSERT(split != NULL);
+ l1 = lwgeom_as_lwline(split->geoms[0]);
+ CU_ASSERT(l1 != NULL);
+ getPoint2d_p(l1->points, 1, &pt);
+ ASSERT_DOUBLE_EQUAL(pt.x, -20);
+ ASSERT_DOUBLE_EQUAL(pt.y, 0);
+ l2 = lwgeom_as_lwline(split->geoms[1]);
+ CU_ASSERT(l2 != NULL);
+ getPoint2d_p(l2->points, 0, &pt);
+ ASSERT_DOUBLE_EQUAL(pt.x, -20);
+ ASSERT_DOUBLE_EQUAL(pt.y, 0);
+ }
+ lwgeom_free(ret);
+ lwgeom_free(geom);
+ lwgeom_free(blade);
}
Modified: trunk/liblwgeom/lwgeom_geos_split.c
===================================================================
--- trunk/liblwgeom/lwgeom_geos_split.c 2016-01-05 16:27:58 UTC (rev 14549)
+++ trunk/liblwgeom/lwgeom_geos_split.c 2016-01-05 16:39:55 UTC (rev 14550)
@@ -22,7 +22,8 @@
*
**********************************************************************/
-
+#include "../postgis_config.h"
+/*#define POSTGIS_DEBUG_LEVEL 4*/
#include "lwgeom_geos.h"
#include "liblwgeom_internal.h"
@@ -208,11 +209,13 @@
lwline_split_by_point_to(const LWLINE* lwline_in, const LWPOINT* blade_in,
LWMLINE* v)
{
- double loc, dist;
+ double mindist = -1;
POINT4D pt, pt_projected;
+ POINT4D p1, p2;
+ POINTARRAY *ipa = lwline_in->points;
POINTARRAY* pa1;
POINTARRAY* pa2;
- double vstol; /* vertex snap tolerance */
+ int i, nsegs, seg = -1;
/* Possible outcomes:
*
@@ -228,30 +231,69 @@
*/
getPoint4d_p(blade_in->point, 0, &pt);
- loc = ptarray_locate_point(lwline_in->points, &pt, &dist, &pt_projected);
- /* lwnotice("Location: %g -- Distance: %g", loc, dist); */
-
- if ( dist > 0 ) /* TODO: accept a tolerance ? */
+ /* Find closest segment */
+ getPoint4d_p(ipa, 0, &p1);
+ nsegs = ipa->npoints - 1;
+ for ( i = 0; i < nsegs; i++ )
{
- /* No intersection */
- return 0;
+ getPoint4d_p(ipa, i+1, &p2);
+ double dist;
+ dist = distance2d_pt_seg((POINT2D*)&pt, (POINT2D*)&p1, (POINT2D*)&p2);
+ LWDEBUGF(4, " Distance of point %g %g to segment %g %g, %g %g: %g", pt.x, pt.y, p1.x, p1.y, p2.x, p2.y, dist);
+ if (i==0 || dist < mindist )
+ {
+ mindist = dist;
+ seg=i;
+ }
+ p1 = p2;
}
- if ( loc == 0 || loc == 1 )
+ LWDEBUGF(3, "Closest segment: %d", seg);
+ LWDEBUGF(3, "mindist: %g", mindist);
+
+ /* No intersection */
+ if ( mindist > 0 ) return 0;
+
+ /* empty or single-point line, intersection on boundary */
+ if ( seg < 0 ) return 1;
+
+ /*
+ * We need to project the
+ * point on the closest segment.
+ */
+ getPoint4d_p(ipa, seg, &p1);
+ getPoint4d_p(ipa, seg+1, &p2);
+ closest_point_on_segment(&pt, &p1, &p2, &pt_projected);
+
+ LWDEBUGF(3, "Projected point:(%g %g), seg:%d, p1:(%g %g), p2:(%g %g)", pt_projected.x, pt_projected.y, seg, p1.x, p1.y, p2.x, p2.y);
+
+ /* When closest point == an endpoint, this is a boundary intersection */
+ if ( ( (seg == nsegs-1) && p4d_same(&pt_projected, &p2) ) ||
+ ( (seg == 0) && p4d_same(&pt_projected, &p1) ) )
{
- /* Intersection is on the boundary */
return 1;
}
- /* There is a real intersection, let's get two substrings */
+ /* This is an internal intersection, let's build the two new pointarrays */
- /* Compute vertex snap tolerance based on line length
- * TODO: take as parameter ? */
- vstol = ptarray_length_2d(lwline_in->points) / 1e14;
+ pa1 = ptarray_construct_empty(FLAGS_GET_Z(ipa->flags), FLAGS_GET_M(ipa->flags), seg+2);
+ /* TODO: replace with a memcpy ? */
+ for (i=0; i<=seg; ++i)
+ {
+ getPoint4d_p(ipa, i, &p1);
+ ptarray_append_point(pa1, &p1, LW_FALSE);
+ }
+ ptarray_append_point(pa1, &pt_projected, LW_FALSE);
- pa1 = ptarray_substring(lwline_in->points, 0, loc, vstol);
- pa2 = ptarray_substring(lwline_in->points, loc, 1, vstol);
+ pa2 = ptarray_construct_empty(FLAGS_GET_Z(ipa->flags), FLAGS_GET_M(ipa->flags), ipa->npoints-seg);
+ ptarray_append_point(pa2, &pt_projected, LW_FALSE);
+ /* TODO: replace with a memcpy (if so need to check for duplicated point) ? */
+ for (i=seg+1; i<ipa->npoints; ++i)
+ {
+ getPoint4d_p(ipa, i, &p1);
+ ptarray_append_point(pa2, &p1, LW_FALSE);
+ }
/* NOTE: I've seen empty pointarrays with loc != 0 and loc != 1 */
if ( pa1->npoints == 0 || pa2->npoints == 0 ) {
Modified: trunk/topology/test/regress/st_modedgesplit.sql
===================================================================
--- trunk/topology/test/regress/st_modedgesplit.sql 2016-01-05 16:27:58 UTC (rev 14549)
+++ trunk/topology/test/regress/st_modedgesplit.sql 2016-01-05 16:39:55 UTC (rev 14550)
@@ -148,3 +148,13 @@
DROP FUNCTION check_changes();
SELECT DropTopology('city_data');
DELETE FROM spatial_ref_sys where srid = 4326;
+
+-- See https://trac.osgeo.org/postgis/ticket/3401
+SELECT 't3401.start', CreateTopology('bug3401') > 1;
+SELECT 't3401.N1', ST_AddIsoNode('bug3401', 0, ST_MakePoint(-180, 0));
+SELECT 't3401.N2', ST_AddIsoNode('bug3401', 0, ST_MakePoint(0, 0));
+SELECT 't3401.L1', ST_AddIsoEdge('bug3401', 1, 2, 'LINESTRING(-180 0, 0 0)'::geometry);
+SELECT 't3401.valid_before', * FROM ValidateTopology('bug3401');
+SELECT 't3401.split', ST_ModEdgeSplit('bug3401', 1, ST_MakePoint(-20, 0));
+SELECT 't3401.valid_after', * FROM ValidateTopology('bug3401');
+SELECT 't3401.end', DropTopology('bug3401');
Modified: trunk/topology/test/regress/st_modedgesplit_expected
===================================================================
--- trunk/topology/test/regress/st_modedgesplit_expected 2016-01-05 16:27:58 UTC (rev 14549)
+++ trunk/topology/test/regress/st_modedgesplit_expected 2016-01-05 16:39:55 UTC (rev 14550)
@@ -42,3 +42,9 @@
robust.1|E1|N3
robust.2|t|t
Topology 'city_data' dropped
+t3401.start|t
+t3401.N1|1
+t3401.N2|2
+t3401.L1|1
+t3401.split|3
+t3401.end|Topology 'bug3401' dropped
More information about the postgis-tickets
mailing list