]> granicus.if.org Git - postgis/commitdiff
Implement and regress-test TopoGeo_AddLineString
authorSandro Santilli <strk@keybit.net>
Tue, 3 Jan 2012 18:41:55 +0000 (18:41 +0000)
committerSandro Santilli <strk@keybit.net>
Tue, 3 Jan 2012 18:41:55 +0000 (18:41 +0000)
git-svn-id: http://svn.osgeo.org/postgis/trunk@8654 b70326c6-7e19-0410-871a-916f4a2858ee

topology/sql/populate.sql
topology/test/Makefile
topology/test/regress/topogeo_addlinestring.sql [new file with mode: 0644]
topology/test/regress/topogeo_addlinestring_expected [new file with mode: 0644]

index 7e1f054e0bc8275a4b5198933213af64de4bcd18..fcba31e116627d16f58ca8facbac27f95218f694 100644 (file)
@@ -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';
index 56c729b3f43fe8fa9e8db8823aa212de11a76e51..787d7569f6ba5e049db9a96d0fa44f993709db5b 100644 (file)
@@ -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 (file)
index 0000000..b6d3f06
--- /dev/null
@@ -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 (file)
index 0000000..99f8699
--- /dev/null
@@ -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