From 18bfdbd23e916bb9fa5e96c3a9b0239a3fadcae0 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Wed, 16 Jan 2013 15:14:06 +0000 Subject: [PATCH] Make ST_ChangeEdgeGeom motion collision detection code more robust The new model avoids a call to GEOSSymDifference but rather checks each candidate node against both "motion ranges" containment. It still constructs something, but only MULTIPOINT, which should be safe. Haven't profiled but the new code should also be faster than the previous. Fixes ticket #2176, includes testcase for it. git-svn-id: http://svn.osgeo.org/postgis/trunk@10984 b70326c6-7e19-0410-871a-916f4a2858ee --- topology/sql/sqlmm.sql.in.c | 81 ++++++++++++------- topology/test/regress/st_changeedgegeom.sql | 8 ++ .../test/regress/st_changeedgegeom_expected | 4 + 3 files changed, 63 insertions(+), 30 deletions(-) diff --git a/topology/sql/sqlmm.sql.in.c b/topology/sql/sqlmm.sql.in.c index 5abb4e20c..cdc35ca9d 100644 --- a/topology/sql/sqlmm.sql.in.c +++ b/topology/sql/sqlmm.sql.in.c @@ -13,7 +13,7 @@ -- -- -/*#define POSTGIS_TOPOLOGY_DEBUG 1*/ +/* #define POSTGIS_TOPOLOGY_DEBUG 1 */ --={ ---------------------------------------------------------------- -- SQL/MM block @@ -2545,10 +2545,10 @@ CREATE OR REPLACE FUNCTION topology.ST_ChangeEdgeGeom(atopology varchar, anedge $$ DECLARE rec RECORD; + rng_info RECORD; -- movement range info oldedge RECORD; range GEOMETRY; -- movement range tmp1 GEOMETRY; - tmp2 GEOMETRY; snode_info RECORD; enode_info RECORD; sql TEXT; @@ -2645,6 +2645,7 @@ BEGIN || '.node WHERE geom && ' || quote_literal(acurve::text) || '::geometry' + -- TODO: skip start_node and end_node ! LOOP IF ST_RelateMatch(rec.relate, 'T********') THEN RAISE EXCEPTION 'SQL/MM Spatial exception - geometry crosses a node'; @@ -2694,51 +2695,71 @@ BEGIN -- Check that the "motion range" doesn't include any node --{ - tmp1 := ST_MakeLine(ST_EndPoint(oldedge.geom), ST_StartPoint(oldedge.geom)); + sql := 'SELECT ST_Collect(geom) as nodes, ' + || 'null::geometry as r1, null::geometry as r2 FROM ' + || quote_ident(atopology) + || '.node WHERE geom && ' + || quote_literal(ST_Collect(ST_Envelope(oldedge.geom), + ST_Envelope(acurve))::text) + || '::geometry AND node_id NOT IN ( ' + || oldedge.start_node || ',' || oldedge.end_node || ')'; #ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'end-to-start: %', ST_AsText(tmp1); + RAISE DEBUG '%', sql; #endif + EXECUTE sql INTO rng_info; + + -- There's no collision if there's no nodes in the combined + -- bbox of old and new edges. + -- + IF NOT ST_IsEmpty(rng_info.nodes) THEN -- { - tmp2 := ST_MakeLine(oldedge.geom, tmp1); - IF ST_NumPoints(tmp2) < 4 THEN - tmp2 := ST_AddPoint(tmp2, ST_StartPoint(oldedge.geom)); - END IF; #ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'Old-ring: %', ST_AsText(tmp2); + RAISE DEBUG '% nodes in the edge movement range bbox: %', + ST_NumGeometries(rng_info.nodes), + ST_AsText(rng_info.nodes) + ; #endif - tmp2 := ST_CollectionExtract(ST_MakeValid(ST_MakePolygon(tmp2)), 3); + + tmp1 := ST_MakeLine(ST_EndPoint(oldedge.geom), ST_StartPoint(oldedge.geom)); #ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'Old-ring (poly): %', ST_AsText(tmp2); + RAISE DEBUG 'end-to-start: %', ST_AsText(tmp1); #endif - range := ST_MakeLine(acurve, tmp1); - IF ST_NumPoints(range) < 4 THEN - range := ST_AddPoint(range, ST_StartPoint(oldedge.geom)); - END IF; + rng_info.r1 := ST_MakeLine(oldedge.geom, tmp1); + IF ST_NumPoints(rng_info.r1) < 4 THEN + rng_info.r1 := ST_AddPoint(rng_info.r1, ST_StartPoint(oldedge.geom)); + END IF; #ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'New-ring: %', ST_AsText(range); + RAISE DEBUG 'Old-ring: %', ST_AsText(rng_info.r1); #endif - range := ST_CollectionExtract(ST_MakeValid(ST_MakePolygon(range)), 3); + rng_info.r1 := ST_CollectionExtract( + ST_MakeValid(ST_MakePolygon(rng_info.r1)), 3); #ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'New-ring (poly): %', ST_AsText(range); + RAISE DEBUG 'Old-ring (poly): %', ST_AsText(rng_info.r1); #endif - range := ST_SymDifference(range, tmp2); + rng_info.r2 := ST_MakeLine(acurve, tmp1); + IF ST_NumPoints(rng_info.r2) < 4 THEN + rng_info.r2 := ST_AddPoint(rng_info.r2, ST_StartPoint(oldedge.geom)); + END IF; #ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'Range motion: %', ST_AsText(range); + RAISE DEBUG 'New-ring: %', ST_AsText(rng_info.r2); #endif - - sql := 'SELECT node_id, geom FROM ' - || quote_ident(atopology) - || '.node WHERE ST_Contains(' - || quote_literal(range::text) - || '::geometry, geom) LIMIT 1'; + rng_info.r2 := ST_CollectionExtract( + ST_MakeValid(ST_MakePolygon(rng_info.r2)), 3); #ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG '%', sql; + RAISE DEBUG 'New-ring (poly): %', ST_AsText(rng_info.r2); #endif - FOR rec IN EXECUTE sql LOOP -- { - RAISE EXCEPTION 'Edge motion collision at %', ST_AsText(rec.geom); - END LOOP; -- } + + tmp1 := ST_CollectionExtract(ST_Intersection(rng_info.nodes, rng_info.r1), 1); + range := ST_CollectionExtract(ST_Intersection(rng_info.nodes, rng_info.r2), 1); + IF ST_IsEmpty(tmp1) != ST_IsEmpty(range) OR NOT ST_Equals(tmp1, range) + THEN + RAISE EXCEPTION 'Edge motion collision at %', + ST_AsText(ST_GeometryN(ST_Union(tmp1, range), 1)); + END IF; + + END IF; -- } --} motion range checking end diff --git a/topology/test/regress/st_changeedgegeom.sql b/topology/test/regress/st_changeedgegeom.sql index f995e47c4..3fa533d27 100644 --- a/topology/test/regress/st_changeedgegeom.sql +++ b/topology/test/regress/st_changeedgegeom.sql @@ -129,6 +129,14 @@ SELECT 'T11F', SELECT 'T12.1', ST_AddIsoNode('city_data', 8, 'POINT(49 10)'); SELECT 'T12', ST_ChangeEdgeGeom('city_data', 16, 'LINESTRING(47 6, 47 14)'); +-- See http://trac.osgeo.org/postgis/ticket/2176 +SELECT 'T13.1', TopoGeo_AddLineString('city_data', '01020000001D000000E42CEC69873FF2BF9E98F56228E347400EDB16653648F2BF4985B18520E34740E92B4833164DF2BF3A1E335019E34740A94D9CDCEF50F2BF33F9669B1BE347407DAEB6627F59F2BF2CF180B229E34740758E01D9EB5DF2BFD0D556EC2FE34740533F6F2A5261F2BFD717096D39E34740F4893C49BA66F2BFC8073D9B55E34740B8239C16BC68F2BF33A7CB6262E34740AA2B9FE57970F2BF4165FCFB8CE347406DC5FEB27B72F2BFBA4E232D95E34740978BF84ECC7AF2BF24EEB1F4A1E34740E527D53E1D8FF2BF8F8D40BCAEE3474036CD3B4ED191F2BF649291B3B0E34740841266DAFE95F2BF1DE6CB0BB0E34740E3361AC05BA0F2BFB2632310AFE347405C5A0D897BACF2BF72F90FE9B7E3474031D3F6AFACB4F2BF4F232D95B7E347402B137EA99FB7F2BFD656EC2FBBE347402D431CEBE2B6F2BF551344DD07E4474011E4A08499B6F2BF15E3FC4D28E447406519E25817B7F2BF63EE5A423EE447409DD7D825AAB7F2BFE3FC4D2844E447405969520ABABDF2BF2384471B47E44740A31EA2D11DC4F2BFB1F9B83654E447400473F4F8BDCDF2BFEA5BE67459E447405070B1A206D3F2BFF19D98F562E4474062670A9DD7D8F2BF0E4FAF9465E447407FF6234564D8F2BFF1BA7EC16EE44740' ); +SELECT 'T13.2', ST_ChangeEdgeGeom('city_data', 29, '010200000008000000E42CEC69873FF2BF9E98F56228E34740E92B4833164DF2BF3B1E335019E34740768E01D9EB5DF2BFD0D556EC2FE347406EC5FEB27B72F2BFBA4E232D95E34740988BF84ECC7AF2BF25EEB1F4A1E347402C137EA99FB7F2BFD656EC2FBBE347409DD7D825AAB7F2BFE4FC4D2844E447407FF6234564D8F2BFF1BA7EC16EE44740' ); +-- Now add an obstacle and try to change back (should fail) +SELECT 'T13.3', TopoGeo_AddPoint('city_data', 'POINT(-1.1697 47.7825)'::geometry); +SELECT 'T13.4', ST_ChangeEdgeGeom('city_data', 29, '01020000001D000000E42CEC69873FF2BF9E98F56228E347400EDB16653648F2BF4985B18520E34740E92B4833164DF2BF3A1E335019E34740A94D9CDCEF50F2BF33F9669B1BE347407DAEB6627F59F2BF2CF180B229E34740758E01D9EB5DF2BFD0D556EC2FE34740533F6F2A5261F2BFD717096D39E34740F4893C49BA66F2BFC8073D9B55E34740B8239C16BC68F2BF33A7CB6262E34740AA2B9FE57970F2BF4165FCFB8CE347406DC5FEB27B72F2BFBA4E232D95E34740978BF84ECC7AF2BF24EEB1F4A1E34740E527D53E1D8FF2BF8F8D40BCAEE3474036CD3B4ED191F2BF649291B3B0E34740841266DAFE95F2BF1DE6CB0BB0E34740E3361AC05BA0F2BFB2632310AFE347405C5A0D897BACF2BF72F90FE9B7E3474031D3F6AFACB4F2BF4F232D95B7E347402B137EA99FB7F2BFD656EC2FBBE347402D431CEBE2B6F2BF551344DD07E4474011E4A08499B6F2BF15E3FC4D28E447406519E25817B7F2BF63EE5A423EE447409DD7D825AAB7F2BFE3FC4D2844E447405969520ABABDF2BF2384471B47E44740A31EA2D11DC4F2BFB1F9B83654E447400473F4F8BDCDF2BFEA5BE67459E447405070B1A206D3F2BFF19D98F562E4474062670A9DD7D8F2BF0E4FAF9465E447407FF6234564D8F2BFF1BA7EC16EE44740' ); + -- TODO: test changing some clockwise closed edges.. SELECT topology.DropTopology('city_data'); + diff --git a/topology/test/regress/st_changeedgegeom_expected b/topology/test/regress/st_changeedgegeom_expected index 34525f93f..102a3223d 100644 --- a/topology/test/regress/st_changeedgegeom_expected +++ b/topology/test/regress/st_changeedgegeom_expected @@ -37,4 +37,8 @@ T11|Edge 16 changed T11F|t T12.1|23 ERROR: Edge motion collision at POINT(49 10) +T13.1|29 +T13.2|Edge 29 changed +T13.3|26 +ERROR: Edge motion collision at POINT(-1.1697 47.7825) Topology 'city_data' dropped -- 2.40.0