From 741da6eab7450e5b50b2290dea96a547ad015d51 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Tue, 3 Jan 2012 18:41:55 +0000 Subject: [PATCH] Implement and regress-test TopoGeo_AddLineString git-svn-id: http://svn.osgeo.org/postgis/trunk@8654 b70326c6-7e19-0410-871a-916f4a2858ee --- topology/sql/populate.sql | 102 +++++++++++++++- topology/test/Makefile | 1 + .../test/regress/topogeo_addlinestring.sql | 115 ++++++++++++++++++ .../regress/topogeo_addlinestring_expected | 95 +++++++++++++++ 4 files changed, 308 insertions(+), 5 deletions(-) create mode 100644 topology/test/regress/topogeo_addlinestring.sql create mode 100644 topology/test/regress/topogeo_addlinestring_expected diff --git a/topology/sql/populate.sql b/topology/sql/populate.sql index 7e1f054e0..fcba31e11 100644 --- a/topology/sql/populate.sql +++ b/topology/sql/populate.sql @@ -642,6 +642,11 @@ DECLARE snapedge GEOMETRY; BEGIN + -- 0. Check arguments + IF geometrytype(apoint) != 'POINT' THEN + RAISE EXCEPTION 'Invalid geometry type (%) passed to TopoGeo_AddPoint, expected POINT', geometrytype(apoint); + END IF; + -- 1. Check if any existing node falls within tolerance -- and if so pick the closest sql := 'SELECT a.node_id FROM ' @@ -671,9 +676,9 @@ BEGIN RAISE DEBUG '%', sql; EXECUTE sql INTO rec; IF rec IS NOT NULL THEN - RAISE DEBUG 'Splitting edge %', rec.edge_id; -- project point to line, split edge by point - prj := st_closestpoint(rec.geom, apoint); + prj := ST_ClosestPoint(rec.geom, apoint); + RAISE DEBUG 'Splitting edge % with closest point %', rec.edge_id, ST_AsText(prj); IF NOT ST_Contains(rec.geom, prj) THEN RAISE DEBUG ' Snapping edge to contain closest point'; -- Snap the edge geometry to the projected point @@ -686,7 +691,7 @@ BEGIN id := topology.ST_ModEdgeSplit(atopology, rec.edge_id, prj); ELSE RAISE DEBUG 'No existing edge within tolerance distance'; - id := ST_AddIsoNode(atopology, NULL, apoint); + id := topology.ST_AddIsoNode(atopology, NULL, apoint); END IF; RETURN id; @@ -700,12 +705,99 @@ LANGUAGE 'plpgsql' VOLATILE; -- -- Add a LineString into a topology -- +-- }{ CREATE OR REPLACE FUNCTION topology.TopoGeo_addLinestring(atopology varchar, aline geometry, tolerance float8 DEFAULT 0) - RETURNS void AS + RETURNS SETOF int AS $$ DECLARE + rec RECORD; + sql TEXT; + set1 GEOMETRY; + set2 GEOMETRY; + snapped GEOMETRY; + noded GEOMETRY; + start_node INTEGER; + end_node INTEGER; + id INTEGER; BEGIN - RAISE EXCEPTION 'TopoGeo_AddLineString not implemented yet'; + + -- 0. Check arguments + IF geometrytype(aline) != 'LINESTRING' THEN + RAISE EXCEPTION 'Invalid geometry type (%) passed to TopoGeo_AddPoint, expected LINESTRING', geometrytype(aline); + END IF; + + -- 1. Self-node + noded := ST_UnaryUnion(aline); + RAISE DEBUG 'Self-noded: %', ST_AsText(noded); + + -- 2. Node to edges and nodes falling within tolerance distance + sql := 'WITH nearby AS ( SELECT geom FROM ' + || quote_ident(atopology) + || '.node WHERE ST_DWithin(geom,' + || quote_literal(aline::text) || '::geometry,' + || tolerance || ') UNION ALL SELECT geom FROM ' + || quote_ident(atopology) + || '.edge WHERE ST_DWithin(geom,' + || quote_literal(aline::text) || '::geometry,' + || tolerance || ') ) SELECT st_collect(geom) FROM nearby;'; + RAISE DEBUG '%', sql; + EXECUTE sql INTO set1; + IF set1 IS NOT NULL THEN + snapped := ST_Snap(noded, set1, tolerance); + RAISE DEBUG 'Snapped: %', ST_AsText(snapped); + noded := ST_Difference(snapped, set1); + RAISE DEBUG 'Difference: %', ST_AsText(noded); + set2 := ST_Intersection(snapped, set1); + RAISE DEBUG 'Intersection: %', ST_AsText(set2); + noded := ST_Union(noded, set2); + END IF; + + -- 3. For each (now-noded) segment, insert an edge + FOR rec IN SELECT (ST_Dump(noded)).geom LOOP + + -- TODO: skip point elements ? + + RAISE DEBUG 'Adding edge %', ST_AsText(rec.geom); + + start_node := topology.TopoGeo_AddPoint(atopology, + ST_StartPoint(rec.geom), + tolerance); + RAISE DEBUG ' Start Node: %', start_node; + + end_node := topology.TopoGeo_AddPoint(atopology, + ST_EndPoint(rec.geom), + tolerance); + RAISE DEBUG ' End Node: %', end_node; + + -- Added endpoints may have drifted due to tolerance, so + -- we need to re-snap the edge to the new nodes before adding it + sql := 'SELECT ST_Collect(geom) FROM ' || quote_ident(atopology) + || '.node WHERE node_id IN (' || start_node || ',' || end_node || ')'; + RAISE DEBUG '%', sql; + EXECUTE sql INTO STRICT set2; + RAISE DEBUG 'Endnodes: %', ST_AsText(set2); + snapped := ST_Snap(rec.geom, set2, tolerance); + RAISE DEBUG 'Snapped edge: %', ST_AsText(snapped); + + -- Check if the so-snapped edge _now_ exists + sql := 'SELECT edge_id FROM ' || quote_ident(atopology) + || '.edge_data WHERE ST_Equals(geom, ' || quote_literal(snapped::text) + || '::geometry)'; + RAISE DEBUG '%', sql; + EXECUTE sql INTO id; + IF id IS NULL THEN + id := topology.ST_AddEdgeModFace(atopology, start_node, end_node, + snapped); + RAISE DEBUG 'New edge id: %', id; + ELSE + RAISE DEBUG 'Old edge id: %', id; + END IF; + + RETURN NEXT id; + + END LOOP; + + RETURN; END $$ LANGUAGE 'plpgsql'; diff --git a/topology/test/Makefile b/topology/test/Makefile index 56c729b3f..787d7569f 100644 --- a/topology/test/Makefile +++ b/topology/test/Makefile @@ -43,6 +43,7 @@ TESTS = regress/legacy_validate.sql regress/legacy_predicate.sql \ regress/topoelement.sql \ regress/topoelementarray_agg.sql \ regress/topogeo_addpoint.sql \ + regress/topogeo_addlinestring \ regress/topogeometry_type.sql \ regress/topo2.5d.sql \ regress/totopogeom.sql \ diff --git a/topology/test/regress/topogeo_addlinestring.sql b/topology/test/regress/topogeo_addlinestring.sql new file mode 100644 index 000000000..b6d3f06ba --- /dev/null +++ b/topology/test/regress/topogeo_addlinestring.sql @@ -0,0 +1,115 @@ +\set VERBOSITY terse +set client_min_messages to ERROR; + +\i load_topology.sql + +-- Save max node id +select 'node'::text as what, max(node_id) INTO city_data.limits FROM city_data.node; +INSERT INTO city_data.limits select 'edge'::text as what, max(edge_id) FROM city_data.edge; +SELECT 'max',* from city_data.limits; + +-- Check changes since last saving, save more +-- { +CREATE OR REPLACE FUNCTION check_changes() +RETURNS TABLE (o text) +AS $$ +DECLARE + rec RECORD; + sql text; +BEGIN + -- Check effect on nodes + sql := 'SELECT n.node_id, ''N|'' || n.node_id || ''|'' || + COALESCE(n.containing_face::text,'''') || ''|'' || + ST_AsText(ST_SnapToGrid(n.geom, 0.2))::text as xx + FROM city_data.node n WHERE n.node_id > ( + SELECT max FROM city_data.limits WHERE what = ''node''::text ) + ORDER BY n.node_id'; + + FOR rec IN EXECUTE sql LOOP + o := rec.xx; + RETURN NEXT; + END LOOP; + + -- Check effect on edges (there should be one split) + sql := ' + WITH node_limits AS ( SELECT max FROM city_data.limits WHERE what = ''node''::text ), + edge_limits AS ( SELECT max FROM city_data.limits WHERE what = ''edge''::text ) + SELECT ''E|'' || e.edge_id || ''|sn'' || e.start_node || ''|en'' || e.end_node :: text as xx + FROM city_data.edge e, node_limits nl, edge_limits el + WHERE e.start_node > nl.max + OR e.end_node > nl.max + OR e.edge_id > el.max + ORDER BY e.edge_id; + '; + + FOR rec IN EXECUTE sql LOOP + o := rec.xx; + RETURN NEXT; + END LOOP; + + UPDATE city_data.limits SET max = (SELECT max(n.node_id) FROM city_data.node n) WHERE what = 'node'; + UPDATE city_data.limits SET max = (SELECT max(e.edge_id) FROM city_data.edge e) WHERE what = 'edge'; + +END; +$$ LANGUAGE 'plpgsql'; +-- } + + +-- Invalid calls +SELECT 'invalid', TopoGeo_addLineString('city_data', 'MULTILINESTRING((36 26, 38 30))'); +SELECT 'invalid', TopoGeo_addLineString('city_data', 'POINT(36 26)'); + +-- Isolated edge in universal face +SELECT 'iso_uni', TopoGeo_addLineString('city_data', 'LINESTRING(36 26, 38 30)'); +SELECT check_changes(); + +-- Isolated point in face 5 +SELECT 'iso_f5', TopoGeo_addLineString('city_data', 'LINESTRING(37 20, 43 19, 41 16)'); +SELECT check_changes(); + +-- Existing isolated edge +SELECT 'iso_ex', TopoGeo_addLineString('city_data', 'LINESTRING(36 26, 38 30)'); +SELECT check_changes(); + +-- Existing isolated edge within tolerance +SELECT 'iso_ex_tol', TopoGeo_addLineString('city_data', 'LINESTRING(36 27, 38 31)', 2); +SELECT check_changes(); + +-- Existing non-isolated edge +SELECT 'noniso_ex', TopoGeo_addLineString('city_data', 'LINESTRING(35 6, 35 14)'); +SELECT check_changes(); + +-- Existing non-isolated edge within tolerance +SELECT 'noniso_ex_tol', TopoGeo_addLineString('city_data', 'LINESTRING(35 7, 35 13)', 2); +SELECT check_changes(); + +-- Fully contained +SELECT 'contained', TopoGeo_addLineString('city_data', 'LINESTRING(35 8, 35 12)'); +SELECT check_changes(); + +-- Overlapping +SELECT 'overlap', TopoGeo_addLineString('city_data', 'LINESTRING(45 22, 49 22)') ORDER BY 2; +SELECT check_changes(); + +-- Crossing +SELECT 'cross', TopoGeo_addLineString('city_data', 'LINESTRING(49 18, 44 17)') ORDER BY 2; +SELECT check_changes(); + +-- Snapping (and splitting a face) +SELECT 'snap', TopoGeo_addLineString('city_data', 'LINESTRING(18 22.2, 22.5 22.2, 21.2 20.5)', 1) ORDER BY 2; +SELECT check_changes(); +SELECT 'snap_again', TopoGeo_addLineString('city_data', 'LINESTRING(18 22.2, 22.5 22.2, 21.2 20.5)', 1) ORDER BY 2; +SELECT check_changes(); + +-- A mix of crossing and overlapping, splitting another face +SELECT 'crossover', TopoGeo_addLineString('city_data', 'LINESTRING(9 18, 9 20, 21 10, 21 7)') ORDER BY 2; +SELECT check_changes(); +SELECT 'crossover_again', TopoGeo_addLineString('city_data', 'LINESTRING(9 18, 9 20, 21 10, 21 7)') ORDER BY 2; +SELECT check_changes(); + +-- Fully containing +SELECT 'contains', TopoGeo_addLineString('city_data', 'LINESTRING(14 34, 13 35, 10 35, 9 35, 7 36)') ORDER BY 2; +SELECT check_changes(); + +DROP FUNCTION check_changes(); +SELECT DropTopology('city_data'); diff --git a/topology/test/regress/topogeo_addlinestring_expected b/topology/test/regress/topogeo_addlinestring_expected new file mode 100644 index 000000000..99f86997f --- /dev/null +++ b/topology/test/regress/topogeo_addlinestring_expected @@ -0,0 +1,95 @@ +BEGIN +t +9 +22 +26 +COMMIT +max|node|22 +max|edge|26 +ERROR: Invalid geometry type (MULTILINESTRING) passed to TopoGeo_AddPoint, expected LINESTRING +ERROR: Invalid geometry type (POINT) passed to TopoGeo_AddPoint, expected LINESTRING +iso_uni|27 +N|23||POINT(36 26) +N|24||POINT(38 30) +E|27|sn23|en24 +iso_f5|28 +N|25||POINT(37 20) +N|26||POINT(41 16) +E|28|sn25|en26 +iso_ex|27 +iso_ex_tol|27 +noniso_ex|18 +noniso_ex_tol|18 +contained|29 +N|27||POINT(35 8) +N|28||POINT(35 12) +E|18|sn10|en27 +E|29|sn27|en28 +E|30|sn28|en13 +overlap|31 +overlap|32 +N|29||POINT(49 22) +N|30||POINT(45 22) +E|8|sn18|en30 +E|31|sn19|en29 +E|32|sn30|en19 +cross|34 +cross|35 +N|31||POINT(49 18) +N|32||POINT(47 17.6) +N|33||POINT(44 17) +E|15|sn12|en32 +E|33|sn32|en19 +E|34|sn31|en32 +E|35|sn32|en33 +snap|7 +snap|36 +snap|39 +N|34||POINT(18 22) +N|35||POINT(22.4 22) +N|36||POINT(21 20.4) +E|6|sn16|en34 +E|7|sn17|en35 +E|19|sn14|en36 +E|36|sn34|en17 +E|37|sn35|en18 +E|38|sn36|en17 +E|39|sn35|en36 +snap_again|7 +snap_again|36 +snap_again|39 +crossover|42 +crossover|44 +crossover|45 +crossover|46 +N|37||POINT(9 20) +N|38||POINT(16.2 14) +N|39||POINT(21 10) +N|40||POINT(9 18) +N|41||POINT(21 7) +E|9|sn15|en38 +E|20|sn9|en41 +E|21|sn15|en40 +E|40|sn37|en16 +E|41|sn38|en14 +E|42|sn37|en38 +E|43|sn39|en14 +E|44|sn38|en39 +E|45|sn40|en37 +E|46|sn41|en39 +crossover_again|42 +crossover_again|44 +crossover_again|45 +crossover_again|46 +contains|25 +contains|47 +contains|48 +contains|49 +N|42||POINT(14 34) +N|43||POINT(7 36) +N|44||POINT(10 35) +E|25|sn21|en44 +E|47|sn42|en22 +E|48|sn21|en43 +E|49|sn44|en22 +Topology 'city_data' dropped -- 2.40.0