]> granicus.if.org Git - postgis/commitdiff
topology.ST_AddEdgeNewFaces implementation and regression test [RT-SIGTA]
authorSandro Santilli <strk@keybit.net>
Wed, 18 May 2011 16:08:36 +0000 (16:08 +0000)
committerSandro Santilli <strk@keybit.net>
Wed, 18 May 2011 16:08:36 +0000 (16:08 +0000)
git-svn-id: http://svn.osgeo.org/postgis/trunk@7194 b70326c6-7e19-0410-871a-916f4a2858ee

topology/sql/sqlmm.sql
topology/test/regress/st_addedgenewfaces.sql
topology/test/regress/st_addedgenewfaces_expected

index 8ef9b8e195f8d4fbcdd0bf53ec73c6991fffd4e8..c1e4aa5e8901ebf7cf10fa22a068ee1d0acfcbd0 100644 (file)
@@ -1828,18 +1828,26 @@ LANGUAGE 'plpgsql' VOLATILE;
 --
 --  ST_AddEdgeNewFaces(atopology, anode, anothernode, acurve)
 --
+-- Not in the specs:
+-- * Reset containing_face for starting and ending point,
+--   as they stop being isolated nodes
+-- * Update references in the Relation table.
+--
 CREATE OR REPLACE FUNCTION topology.ST_AddEdgeNewFaces(atopology varchar, anode integer, anothernode integer, acurve geometry)
   RETURNS INTEGER AS
 $$
 DECLARE
   rec RECORD;
   i INTEGER;
+  topoid INTEGER;
   az FLOAT8;
-  azdif FLOAT8;
-  myaz FLOAT8;
-  minazimuth FLOAT8;
-  maxazimuth FLOAT8;
-  p2 GEOMETRY;
+  span RECORD; -- start point analysis data
+  epan RECORD; --   end point analysis data
+  fan RECORD; -- face analisys
+  newedge RECORD; -- informations about new edge
+  sql TEXT;
+  newfaces INTEGER[];
+  newface INTEGER;
 BEGIN
 
   --
@@ -1868,20 +1876,56 @@ BEGIN
     RAISE EXCEPTION 'SQL/MM Spatial exception - curve not simple';
   END IF;
 
+  --
+       -- Get topology id
+  --
+  BEGIN
+    SELECT id FROM topology.topology
+      INTO STRICT topoid WHERE name = atopology;
+    EXCEPTION
+      WHEN NO_DATA_FOUND THEN
+        RAISE EXCEPTION 'SQL/MM Spatial exception - invalid topology name';
+  END;
+
+  -- Initialize new edge info (will be filled up more later)
+  SELECT anode as start_node, anothernode as end_node, acurve as geom,
+    NULL::int as next_left_edge, NULL::int as next_right_edge,
+    NULL::int as left_face, NULL::int as right_face, NULL::int as edge_id,
+    NULL::int as prev_left_edge, NULL::int as prev_right_edge, -- convenience
+    anode = anothernode as isclosed, -- convenience
+    false as start_node_isolated, -- convenience
+    false as end_node_isolated -- convenience
+  INTO newedge;
+
   -- 
-  -- Check endpoints existance and match with Curve geometry
+  -- Check endpoints existance, match with Curve geometry
+  -- and get face information (if any)
   --
-  i=0;
-  FOR rec IN EXECUTE 'SELECT '
+  i := 0;
+  FOR rec IN EXECUTE 'SELECT node_id, '
     || ' CASE WHEN node_id = ' || anode
     || ' THEN 1 WHEN node_id = ' || anothernode
-    || ' THEN 0 END AS start, geom FROM '
+    || ' THEN 0 END AS start, containing_face, geom FROM '
     || quote_ident(atopology)
     || '.node '
     || ' WHERE node_id IN ( '
     || anode || ',' || anothernode
     || ')'
   LOOP
+    IF rec.containing_face IS NOT NULL THEN
+      RAISE DEBUG  'containing_face for node %:%',
+        rec.node_id, rec.containing_face;
+      IF newedge.left_face IS NULL THEN
+        newedge.left_face := rec.containing_face;
+        newedge.right_face := rec.containing_face;
+      ELSE
+        IF newedge.left_face != rec.containing_face THEN
+          RAISE EXCEPTION
+            'SQL/MM Spatial exception - geometry crosses an edge (endnodes in faces % and %)', newedge.left_face, rec.containing_face;
+        END IF;
+      END IF;
+    END IF;
+
     IF rec.start THEN
       IF NOT Equals(rec.geom, ST_StartPoint(acurve))
       THEN
@@ -1896,10 +1940,10 @@ BEGIN
       END IF;
     END IF;
 
-    i=i+1;
+    i := i + 1;
   END LOOP;
 
-  IF anode != anothernode THEN
+  IF NOT newedge.isclosed THEN
     IF i < 2 THEN
     RAISE EXCEPTION
      'SQL/MM Spatial exception - non-existent node';
@@ -1914,15 +1958,17 @@ BEGIN
   --
   -- Check if this geometry crosses any node
   --
-  FOR rec IN EXECUTE 'SELECT node_id FROM '
+  FOR rec IN EXECUTE
+    'SELECT node_id, ST_Relate(geom, '
+    || quote_literal(acurve::text) || '::geometry, 2) as relate FROM '
     || quote_ident(atopology)
     || '.node WHERE geom && '
     || quote_literal(acurve::text)
-    || '::geometry AND ST_Within(geom, '
-    || quote_literal(acurve::text) || '::geometry)'
+    || '::geometry'
   LOOP
-    RAISE EXCEPTION
-    'SQL/MM Spatial exception - geometry crosses a node';
+    IF ST_RelateMatch(rec.relate, 'T********') THEN
+      RAISE EXCEPTION 'SQL/MM Spatial exception - geometry crosses a node';
+    END IF;
   END LOOP;
 
   --
@@ -1930,7 +1976,7 @@ BEGIN
   --
   FOR rec IN EXECUTE 'SELECT edge_id, ST_Relate(geom,' 
     || quote_literal(acurve::text)
-    || '::geometry) as im FROM '
+    || '::geometry, 2) as im FROM '
     || quote_ident(atopology)
     || '.edge_data WHERE geom && '
     || quote_literal(acurve::text) || '::geometry'
@@ -1962,68 +2008,455 @@ BEGIN
 
   ---------------------------------------------------------------
   --
-  -- All checks passed, time to extract informations about
-  -- endpoints:
-  --
-  --  next_left_edge
-  --  next_right_edge
-  --  left_face
-  --  right_face
+  -- All checks passed, time to prepare the new edge
   --
   ---------------------------------------------------------------
 
-  --
-  --
-  -- Compute next_left_edge 
-  --
-  -- We fetch all edges with an endnode equal to
-  -- this edge end_node (anothernode).
-  -- For each edge we compute azimuth of the segment(s).
-  -- Of interest are the edges with closest (smaller
-  -- and bigger) azimuths then the azimuth of
-  -- this edge last segment.
-  --
-
-  myaz = ST_Azimuth(ST_EndPoint(acurve), ST_PointN(acurve, ST_NumPoints(acurve)-1));
-  RAISE NOTICE 'My end-segment azimuth: %', myaz;
-  FOR rec IN EXECUTE 'SELECT '
-    || 'edge_id, end_node, start_node, geom'
+       EXECUTE 'SELECT nextval(' || quote_literal(
+                       quote_ident(atopology) || '.edge_data_edge_id_seq') || ')'
+  INTO STRICT newedge.edge_id;
+
+  -- Compute azimut of first edge end on start node
+  SELECT null::int AS nextCW, null::int AS nextCCW,
+         null::float8 AS minaz, null::float8 AS maxaz,
+         false AS was_isolated,
+         ST_Azimuth(ST_StartPoint(acurve),
+                    ST_PointN(acurve, 2)) AS myaz
+        -- TODO: second point might be equals to first point...
+        --       ... we should check that and find a better candidate
+  INTO span;
+
+  -- Compute azimuth of last edge end on end node
+  SELECT null::int AS nextCW, null::int AS nextCCW,
+         null::float8 AS minaz, null::float8 AS maxaz,
+         false AS was_isolated,
+         ST_Azimuth(ST_EndPoint(acurve),
+                    ST_PointN(acurve, ST_NumPoints(acurve)-1)) AS myaz
+        -- TODO: one-to-last point might be equals to last point...
+        --       ... we should check that and find a better candidate
+  INTO epan;
+
+
+  -- Find links on start node -- {
+
+  RAISE DEBUG 'My start-segment azimuth: %', span.myaz;
+
+  sql :=
+    'SELECT edge_id, -1 AS end_node, start_node, left_face, right_face, geom'
     || ' FROM '
     || quote_ident(atopology)
-    || '.edge_data '
-    || ' WHERE '
-    || ' end_node = ' || anothernode
-    || ' OR '
-    || ' start_node = ' || anothernode
-  LOOP
+    || '.edge_data WHERE start_node = ' || anode
+    || ' UNION SELECT edge_id, end_node, -1, left_face, right_face, geom FROM '
+    || quote_ident(atopology)
+    || '.edge_data WHERE end_node = ' || anode;
+  IF newedge.isclosed THEN
+    sql := sql || ' UNION SELECT '
+      || newedge.edge_id || ',' || newedge.end_node
+      || ',-1,0,0,' -- pretend we start elsewhere
+      || quote_literal(newedge.geom::text);
+  END IF;
+  i := 0;
+  FOR rec IN EXECUTE sql
+  LOOP -- incident edges {
 
-    IF rec.start_node = anothernode THEN
+    i := i + 1;
+
+    IF rec.start_node = anode THEN
       --
       -- Edge starts at our node, we compute
       -- azimuth from node to its second point
       --
-      az = ST_Azimuth(ST_EndPoint(acurve),
-        ST_PointN(rec.geom, 2));
+      az := ST_Azimuth(ST_StartPoint(acurve), ST_PointN(rec.geom, 2));
+
+    ELSE
+      --
+      -- Edge ends at our node, we compute
+      -- azimuth from node to its second-last point
+      --
+      az := ST_Azimuth(ST_StartPoint(acurve),
+                       ST_PointN(rec.geom, ST_NumPoints(rec.geom)-1));
+      rec.edge_id := -rec.edge_id;
+
+    END IF;
 
-      RAISE NOTICE 'Edge % starts at node % - azimuth %',
-        rec.edge_id, rec.start_node, az;
+    RAISE DEBUG 'Edge % - az % (%) - fl:% fr:%',
+      rec.edge_id, az, az - span.myaz, rec.left_face, rec.right_face;
+
+    az = az - span.myaz;
+    IF az < 0 THEN
+      az := az + 2*PI();
+    END IF;
+
+    -- RAISE DEBUG ' normalized az %', az;
+
+    IF span.maxaz IS NULL OR az > span.maxaz THEN
+      span.maxaz := az;
+      span.nextCCW := rec.edge_id;
+      IF abs(rec.edge_id) != newedge.edge_id THEN
+        IF rec.edge_id < 0 THEN
+          -- TODO: check for mismatch ?
+          newedge.left_face := rec.left_face;
+        ELSE
+          -- TODO: check for mismatch ?
+          newedge.left_face := rec.right_face;
+        END IF;
+      END IF;
     END IF;
 
-    IF rec.end_node = anothernode THEN
+    IF span.minaz IS NULL OR az < span.minaz THEN
+      span.minaz := az;
+      span.nextCW := rec.edge_id;
+      IF abs(rec.edge_id) != newedge.edge_id THEN
+        IF rec.edge_id < 0 THEN
+          -- TODO: check for mismatch ?
+          newedge.right_face := rec.right_face;
+        ELSE
+          -- TODO: check for mismatch ?
+          newedge.right_face := rec.left_face;
+        END IF;
+      END IF;
+    END IF;
+
+    --RAISE DEBUG 'Closest edges: CW:%(%) CCW:%(%)', span.nextCW, span.minaz, span.nextCCW, span.maxaz;
+
+  END LOOP; -- incident edges }
+
+  RAISE DEBUG 'span ROW_COUNT: %', i;
+  IF newedge.isclosed THEN
+    IF i < 2 THEN span.was_isolated = true; END IF;
+  ELSE
+    IF i < 1 THEN span.was_isolated = true; END IF;
+  END IF;
+
+  IF span.nextCW IS NULL THEN
+    -- This happens if the destination node is isolated
+    newedge.next_right_edge := newedge.edge_id;
+    newedge.prev_left_edge := -newedge.edge_id;
+  ELSE
+    newedge.next_right_edge := span.nextCW;
+    newedge.prev_left_edge := -span.nextCCW;
+  END IF;
+
+  RAISE DEBUG 'edge:%', newedge.edge_id;
+  RAISE DEBUG ' left:%, next:%, prev:%',
+    newedge.left_face, newedge.next_left_edge, newedge.prev_left_edge;
+  RAISE DEBUG ' right:%, next:%, prev:%',
+    newedge.right_face, newedge.next_right_edge, newedge.prev_right_edge;
+
+  -- } start_node analysis
+
+
+  -- Find links on end_node {
+      
+  RAISE DEBUG 'My end-segment azimuth: %', epan.myaz;
+
+  sql :=
+    'SELECT edge_id, -1 as end_node, start_node, left_face, right_face, geom'
+    || ' FROM '
+    || quote_ident(atopology)
+    || '.edge_data WHERE start_node = ' || anothernode
+    || 'UNION SELECT edge_id, end_node, -1, left_face, right_face, geom'
+    || ' FROM '
+    || quote_ident(atopology)
+    || '.edge_data WHERE end_node = ' || anothernode;
+  IF newedge.isclosed THEN
+    sql := sql || ' UNION SELECT '
+      || newedge.edge_id || ',' || -1 -- pretend we end elsewhere
+      || ',' || newedge.start_node || ',0,0,'
+      || quote_literal(newedge.geom::text);
+  END IF;
+  i := 0;
+  FOR rec IN EXECUTE sql
+  LOOP -- incident edges {
+
+    i := i + 1;
+
+    IF rec.start_node = anothernode THEN
+      --
+      -- Edge starts at our node, we compute
+      -- azimuth from node to its second point
+      --
+      az := ST_Azimuth(ST_EndPoint(acurve), ST_PointN(rec.geom, 2));
+
+    ELSE
       --
       -- Edge ends at our node, we compute
       -- azimuth from node to its second-last point
       --
-      az = ST_Azimuth(ST_EndPoint(acurve),
+      az := ST_Azimuth(ST_EndPoint(acurve),
         ST_PointN(rec.geom, ST_NumPoints(rec.geom)-1));
+      rec.edge_id := -rec.edge_id;
 
-      RAISE NOTICE 'Edge % ends at node % - azimuth %',
-        rec.edge_id, rec.end_node, az;
     END IF;
-  END LOOP;
 
+    RAISE DEBUG 'Edge % - az % (%)', rec.edge_id, az, az - epan.myaz;
+
+    az := az - epan.myaz;
+    IF az < 0 THEN
+      az := az + 2*PI();
+    END IF;
+
+    -- RAISE DEBUG ' normalized az %', az;
+
+    IF epan.maxaz IS NULL OR az > epan.maxaz THEN
+      epan.maxaz := az;
+      epan.nextCCW := rec.edge_id;
+      IF abs(rec.edge_id) != newedge.edge_id THEN
+        IF rec.edge_id < 0 THEN
+          -- TODO: check for mismatch ?
+          newedge.right_face := rec.left_face;
+        ELSE
+          -- TODO: check for mismatch ?
+          newedge.right_face := rec.right_face;
+        END IF;
+      END IF;
+    END IF;
+
+    IF epan.minaz IS NULL OR az < epan.minaz THEN
+      epan.minaz := az;
+      epan.nextCW := rec.edge_id;
+      IF abs(rec.edge_id) != newedge.edge_id THEN
+        IF rec.edge_id < 0 THEN
+          -- TODO: check for mismatch ?
+          newedge.left_face := rec.right_face;
+        ELSE
+          -- TODO: check for mismatch ?
+          newedge.left_face := rec.left_face;
+        END IF;
+      END IF;
+    END IF;
+
+    --RAISE DEBUG 'Closest edges: CW:%(%) CCW:%(%)', epan.nextCW, epan.minaz, epan.nextCCW, epan.maxaz;
+
+  END LOOP; -- incident edges }
+
+  RAISE DEBUG 'epan ROW_COUNT: %', i;
+  IF newedge.isclosed THEN
+    IF i < 2 THEN epan.was_isolated = true; END IF;
+  ELSE
+    IF i < 1 THEN epan.was_isolated = true; END IF;
+  END IF;
+
+  IF epan.nextCW IS NULL THEN
+    -- This happens if the destination node is isolated
+    newedge.next_left_edge := -newedge.edge_id;
+    newedge.prev_right_edge := newedge.edge_id;
+  ELSE
+    newedge.next_left_edge := epan.nextCW;
+    newedge.prev_right_edge := -epan.nextCCW;
+  END IF;
+
+  -- } end_node analysis
+
+  RAISE DEBUG 'edge:%', newedge.edge_id;
+  RAISE DEBUG ' left:%, next:%, prev:%',
+    newedge.left_face, newedge.next_left_edge, newedge.prev_left_edge;
+  RAISE DEBUG ' right:%, next:%, prev:%',
+    newedge.right_face, newedge.next_right_edge, newedge.prev_right_edge;
+
+  ----------------------------------------------------------------------
+  --
+  -- If we don't have faces setup by now we must have encountered
+  -- a malformed topology (no containing_face on isolated nodes, no
+  -- left/right faces on adjacent edges or mismatching values)
+  --
+  ----------------------------------------------------------------------
+  IF newedge.left_face != newedge.right_face THEN
+    RAISE EXCEPTION 'Left(%)/right(%) faces mismatch: invalid topology ?', 
+      newedge.left_face, newedge.right_face;
+  END IF;
+  IF newedge.left_face IS NULL THEN
+    RAISE EXCEPTION 'Could not derive edge face from linked primitives: invalid topology ?';
+  END IF;
+
+  ----------------------------------------------------------------------
+  --
+  -- Polygonize the current edges (to see later if the addition
+  -- of the new one created another ring)
+  --
+  ----------------------------------------------------------------------
+
+  SELECT null::geometry as post, null::geometry as pre INTO fan;
+
+  EXECUTE
+    'SELECT ST_Polygonize(geom) FROM '
+    || quote_ident(atopology) || '.edge_data WHERE left_face = '
+    || newedge.left_face || ' OR right_face = ' || newedge.right_face
+    INTO STRICT fan.pre;
+
+  ----------------------------------------------------------------------
+  --
+  -- Insert the new edge, and update all linking
+  --
+  ----------------------------------------------------------------------
+
+  -- Insert the new edge with what we have so far
+  EXECUTE 'INSERT INTO ' || quote_ident(atopology) 
+               || '.edge VALUES(' || newedge.edge_id
+    || ',' || newedge.start_node
+               || ',' || newedge.end_node
+               || ',' || newedge.next_left_edge
+               || ',' || newedge.next_right_edge
+               || ',' || newedge.left_face
+               || ',' || newedge.right_face
+               || ',' || quote_literal(newedge.geom::geometry::text)
+               || ')';
+
+  -- Link prev_left_edge to us 
+  -- (if it's not us already)
+  IF abs(newedge.prev_left_edge) != newedge.edge_id THEN
+    IF newedge.prev_left_edge > 0 THEN
+      -- its next_left_edge is us
+      EXECUTE 'UPDATE ' || quote_ident(atopology)
+        || '.edge_data SET next_left_edge = '
+        || newedge.edge_id
+        || ', abs_next_left_edge = '
+        || newedge.edge_id
+        || ' WHERE edge_id = ' 
+        || newedge.prev_left_edge;
+    ELSE
+      -- its next_right_edge is us
+      EXECUTE 'UPDATE ' || quote_ident(atopology)
+        || '.edge_data SET next_right_edge = '
+        || newedge.edge_id
+        || ', abs_next_right_edge = '
+        || newedge.edge_id
+        || ' WHERE edge_id = ' 
+        || -newedge.prev_left_edge;
+    END IF;
+  END IF;
+
+  -- Link prev_right_edge to us 
+  -- (if it's not us already)
+  IF abs(newedge.prev_right_edge) != newedge.edge_id THEN
+    IF newedge.prev_right_edge > 0 THEN
+      -- its next_left_edge is -us
+      EXECUTE 'UPDATE ' || quote_ident(atopology)
+        || '.edge_data SET next_left_edge = '
+        || -newedge.edge_id
+        || ', abs_next_left_edge = '
+        || newedge.edge_id
+        || ' WHERE edge_id = ' 
+        || newedge.prev_right_edge;
+    ELSE
+      -- its next_right_edge is -us
+      EXECUTE 'UPDATE ' || quote_ident(atopology)
+        || '.edge_data SET next_right_edge = '
+        || -newedge.edge_id
+        || ', abs_next_right_edge = '
+        || newedge.edge_id
+        || ' WHERE edge_id = ' 
+        || -newedge.prev_right_edge;
+    END IF;
+  END IF;
+
+  -- NOT IN THE SPECS...
+  -- set containing_face = null for start_node and end_node
+  -- if they where isolated 
+  IF span.was_isolated OR epan.was_isolated THEN
+      EXECUTE 'UPDATE ' || quote_ident(atopology)
+        || '.node SET containing_face = null WHERE node_id IN ('
+        || anode || ',' || anothernode || ')';
+  END IF;
+
+  ----------------------------------------------------------------------
+  --
+  -- Polygonize the new edges and see if the addition created a new ring
+  --
+  ----------------------------------------------------------------------
+
+  EXECUTE 'SELECT ST_Polygonize(geom) FROM '
+    || quote_ident(atopology) || '.edge_data WHERE left_face = '
+    || newedge.left_face || ' OR right_face = ' || newedge.right_face
+    INTO STRICT fan.post;
+
+  IF ST_NumGeometries(fan.pre) = ST_NumGeometries(fan.post) THEN
+    -- all done, I hope
+    RETURN newedge.edge_id;
+  END IF;
+
+  RAISE WARNING 'ST_AddEdgeNewFaces: edge % splitted face %',
+      newedge.edge_id, newedge.left_face;
+
+  IF newedge.left_face != 0 THEN
+
+    -- Set old face edges to zero to let AddFace do something with them
+    EXECUTE 'UPDATE ' || quote_ident(atopology)
+      || '.edge_data SET left_face = 0 WHERE left_face = '
+      || newedge.left_face;
+    EXECUTE 'UPDATE ' || quote_ident(atopology)
+      || '.edge_data SET right_face = 0 WHERE right_face = '
+      || newedge.left_face;
+    -- Now we call topology.AddFace for each of the two new
+    -- faces. These are the ones that do contain the new edge
+    -- The ORDER serves predictability of which face is added first
+    FOR rec IN SELECT geom FROM ST_Dump(fan.post)
+      ORDER BY ST_XMin(geom), ST_YMin(geom)
+    LOOP -- {
+      RAISE DEBUG 'Adding face %', ST_AsText(rec.geom);
+      sql :=
+        'SELECT topology.AddFace(' || quote_literal(atopology)
+        || ', ' || quote_literal(rec.geom::text) || ')';
+      EXECUTE sql INTO newface;
+      newfaces := array_append(newfaces, newface);
+    END LOOP; --}
+
+    RAISE DEBUG 'Added faces: %', newfaces;
+
+    -- NOT IN THE SPECS:
+    -- update TopoGeometry compositions to substitute oldface with newfaces
+    sql := 'UPDATE '
+      || quote_ident(atopology)
+      || '.relation r set element_id = ' || newfaces[1]
+      || ' FROM topology.layer l '
+      || ' WHERE l.topology_id = ' || topoid
+      || ' AND l.level = 0 '
+      || ' AND l.layer_id = r.layer_id '
+      || ' AND r.element_id = ' || newedge.left_face
+      || ' AND r.element_type = 3 RETURNING r.topogeo_id, r.layer_id';
+    --RAISE DEBUG 'SQL: %', sql;
+    FOR rec IN EXECUTE sql
+    LOOP
+      RAISE DEBUG 'TopoGeometry % in layer % contained the face being split (%) - updating to contain both new faces %', rec.topogeo_id, rec.layer_id, newedge.left_face, newfaces;
+
+      -- Add reference to the other face
+      sql := 'INSERT INTO ' || quote_ident(atopology)
+        || '.relation VALUES( ' || rec.topogeo_id
+        || ',' || rec.layer_id || ',' || newfaces[2] || ', 3)';
+      --RAISE DEBUG 'SQL: %', sql;
+      EXECUTE sql;
+
+    END LOOP;
+
+    -- drop old face from faces table
+    sql := 'DELETE FROM ' || quote_ident(atopology)
+      || '.face WHERE face_id = ' || newedge.left_face;
+    EXECUTE sql;
+
+  ELSE
+
+    FOR rec IN SELECT (ST_Dump(fan.post)).geom
+    LOOP -- {
+      -- skip the polygons whose boundary does not contain
+      -- the newly added edge
+      IF NOT ST_Contains(ST_Boundary(rec.geom), acurve) THEN
+        CONTINUE;
+      END IF;
+
+      RAISE DEBUG 'Adding face %', ST_AsText(rec.geom);
+      sql :=
+        'SELECT topology.AddFace(' || quote_literal(atopology)
+        || ', ' || quote_literal(rec.geom::text) || ')';
+      EXECUTE sql INTO newface;
+      newfaces := array_append(newfaces, newface);
+    END LOOP; --}
+
+    RAISE DEBUG 'Added faces: %', newfaces;
+
+  END IF;
 
-  RAISE EXCEPTION 'Not implemented yet';
+  RETURN newedge.edge_id;
 END
 $$
 LANGUAGE 'plpgsql' VOLATILE;
index 11cb5c3aee1b6d76fea0fe21baea24acfe56cad4..0450953708f9f85d3dffa8cbe7a97a8278c359c8 100644 (file)
@@ -2,7 +2,6 @@ set client_min_messages to ERROR;
 
 \i load_topology.sql
 
-
 -- Endpoint / node mismatch
 SELECT topology.ST_AddEdgeNewFaces('city_data', 7, 6,
  'LINESTRING(36 38,57 33)');
@@ -39,6 +38,276 @@ SELECT 'O', topology.ST_AddEdgeNewFaces('city_data', 5, 6,
 SELECT 'O', topology.ST_AddEdgeNewFaces('city_data', 5, 6,
  'LINESTRING(36 38,38 35,57 33)');
 
--- TODO: check succeeding ones...
+---------------------------------------------------------------------
+-- Define some features
+---------------------------------------------------------------------
+
+CREATE TABLE city_data.fp(id varchar);
+SELECT 'L' || topology.AddTopoGeometryColumn('city_data',
+  'city_data', 'fp', 'g', 'POLYGON');
+
+-- Feature composed by face 3 and face 4
+INSERT INTO city_data.fp VALUES ('F3,F4',
+  topology.CreateTopoGeom('city_data', 3, 1, '{{3,3},{4,3}}')); 
+
+CREATE TABLE city_data.fc(id varchar);
+SELECT 'L' || topology.AddTopoGeometryColumn('city_data',
+  'city_data', 'fc', 'g', 'COLLECTION');
+
+-- Feature composed by face 5 and node 4 
+INSERT INTO city_data.fc VALUES ('F5,N4',
+  topology.CreateTopoGeom('city_data', 4, 2, '{{5,3},{4,1}}')); 
+
+
+---------------------------------------------------------------------
+-- Now add some edges splitting faces...
+---------------------------------------------------------------------
+
+--
+-- start node has:
+--    outward edge on the left face
+--    inward edge on the right face
+-- end node has:
+--    inward edge on the left face
+--    inward edge on the right face
+--
+SELECT 1 as id, topology.st_addedgenewfaces('city_data', 14, 18,
+  'LINESTRING(21 14, 35 22)') as edge_id INTO newedge;
+SELECT 'T1', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (19, 7, 17, 10, 
+  ( SELECT edge_id FROM newedge WHERE id = 1 ) )
+  ORDER BY edge_id;
+
+--
+-- start node has:
+--    inward edge on the left face
+--    outward edge on the right face
+-- end node has:
+--    inward edge on the left face
+--    outward edge on the right face
+--
+INSERT INTO newedge SELECT 2, topology.st_addedgenewfaces('city_data',
+  12, 18, 'LINESTRING(47 14, 35 22)');
+SELECT 'T2', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (17, 8, 15, 11, 
+  ( SELECT edge_id FROM newedge WHERE id = 2 ) )
+  ORDER BY edge_id;
+
+--
+-- start node has:
+--    inward edge on the left face
+--    inward edge on the right face
+-- end node has:
+--    outward edge on the left face
+--    outward edge on the right face
+--
+INSERT INTO newedge SELECT 3, topology.st_addedgenewfaces('city_data',
+  12, 10, 'LINESTRING(47 14, 35 6)');
+SELECT 'T3', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (11, 16, 14, 18, 
+  ( SELECT edge_id FROM newedge WHERE id = 3 ) )
+  ORDER BY edge_id;
+
+--
+-- start node has:
+--    outward edge on the left face
+--    outward edge on the right face
+-- end node has:
+--    outward edge on the left face
+--    inward edge on the right face
+--
+INSERT INTO newedge SELECT 4, topology.st_addedgenewfaces('city_data',
+  9, 13, 'LINESTRING(21 6, 35 14)');
+SELECT 'T4', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (20, 10, 18, 13, 
+  ( SELECT edge_id FROM newedge WHERE id = 4 ) )
+  ORDER BY edge_id;
+
+--
+-- Same edge on start and end node, for left face, swapped direction
+--
+INSERT INTO newedge SELECT 5, topology.st_addedgenewfaces('city_data',
+  14, 9, 'LINESTRING(21 14, 19 10, 21 6)');
+SELECT 'T5', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (9, 12, 20,
+  ( SELECT edge_id FROM newedge WHERE id = 5 ) )
+  ORDER BY edge_id;
+
+--
+-- Same edge on start and end node, for left face, same direction
+--
+INSERT INTO newedge SELECT 6, topology.st_addedgenewfaces('city_data',
+  8, 15, 'LINESTRING(9 6, 11 10, 9 14)');
+SELECT 'T6', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (9, 12, 22,
+  ( SELECT edge_id FROM newedge WHERE id = 6 ) )
+  ORDER BY edge_id;
+
+--
+-- Same edge on start and end node, for right face, swapped direction
+--
+INSERT INTO newedge SELECT 7, topology.st_addedgenewfaces('city_data',
+  17, 16, 'LINESTRING(21 22, 15 20, 9 22)');
+SELECT 'T7', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (21, 6, 19,
+  ( SELECT edge_id FROM newedge WHERE id = 7 ) )
+  ORDER BY edge_id;
+
+--
+-- Same edge on start and end node, for right face, same direction
+--
+INSERT INTO newedge SELECT 8, topology.st_addedgenewfaces('city_data',
+  15, 14, 'LINESTRING(9 14, 15 16, 21 14)');
+SELECT 'T8', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (9, 21, 19,
+  ( SELECT edge_id FROM newedge WHERE id = 8 ) )
+  ORDER BY edge_id;
+
+--
+-- Closed edge, counterclockwise, in universe face, next right
+--
+INSERT INTO newedge SELECT 9, topology.st_addedgenewfaces('city_data',
+  9, 9, 'LINESTRING(21 6, 18 0, 24 0, 21 6)');
+SELECT 'T9', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (12, 13,
+  ( SELECT edge_id FROM newedge WHERE id = 9 ) )
+  ORDER BY edge_id;
+
+--
+-- Closed edge, clockwise, in universe face, next right
+--
+INSERT INTO newedge SELECT 10, topology.st_addedgenewfaces('city_data',
+  10, 10, 'LINESTRING(35 6, 38 0, 32 0, 35 6)');
+SELECT 'T10', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (13, 14,
+  ( SELECT edge_id FROM newedge WHERE id = 10 ) )
+  ORDER BY edge_id;
+
+--
+-- Closed edge, clockwise, in universe face, next left
+--
+INSERT INTO newedge SELECT 11, topology.st_addedgenewfaces('city_data',
+  15, 15, 'LINESTRING(9 14, 3 11, 3 17, 9 14)');
+SELECT 'T11', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (21, 22,
+  ( SELECT edge_id FROM newedge WHERE id = 11 ) )
+  ORDER BY edge_id;
+
+--
+-- Closed edge, clockwise, in universe face, against closed edge
+--
+INSERT INTO newedge SELECT 12, topology.st_addedgenewfaces('city_data',
+  1, 1, 'LINESTRING(8 30, 5 27, 11 27, 8 30)');
+SELECT 'T12', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (1, 
+  ( SELECT edge_id FROM newedge WHERE id = 12 ) )
+  ORDER BY edge_id;
+
+--
+-- Closed edge, counterclockwise, in universe face, against closed edge
+--
+INSERT INTO newedge SELECT 13, topology.st_addedgenewfaces('city_data',
+  2, 2, 'LINESTRING(25 30, 28 27, 22 27, 25 30)');
+SELECT 'T13', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (2, 
+  ( SELECT edge_id FROM newedge WHERE id = 13 ) )
+  ORDER BY edge_id;
+
+--
+-- Dangling edge, ending into closed edge endpoint
+--
+INSERT INTO city_data.node(geom, containing_face)
+  VALUES ('POINT(9 33)', 1); -- N23
+INSERT INTO newedge SELECT 14, topology.st_addedgenewfaces('city_data',
+  23, 1, 'LINESTRING(9 33, 8 30)');
+SELECT 'T14', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (1, 
+  ( SELECT edge_id FROM newedge WHERE id = 14 ) )
+  ORDER BY edge_id;
+SELECT 'N' || node_id, containing_face
+  FROM city_data.node WHERE node_id = 23;
+
+--
+-- Dangling edge, originating from closed edge endpoint
+--
+INSERT INTO city_data.node(geom, containing_face)
+  VALUES ('POINT(12 28)', 0); -- N24
+INSERT INTO newedge SELECT 15, topology.st_addedgenewfaces('city_data',
+  1, 24,  'LINESTRING(8 30, 12 28)');
+SELECT 'T15', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (38, 1,
+  ( SELECT edge_id FROM newedge WHERE id = 15 ) )
+  ORDER BY edge_id;
+SELECT 'N' || node_id, containing_face
+  FROM city_data.node WHERE node_id = 24;
+
+--
+-- Closed edge on isolated node
+--
+INSERT INTO newedge SELECT 16, topology.st_addedgenewfaces('city_data',
+  4, 4, 'LINESTRING(20 37, 23 37, 20 34, 20 37)');
+SELECT 'T16', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (2, 3,
+  ( SELECT edge_id FROM newedge WHERE id = 16 ) )
+  ORDER BY edge_id;
+SELECT 'N' || node_id, containing_face FROM city_data.node WHERE node_id = 4;
+
+--
+-- Isolated edge
+--
+INSERT INTO city_data.node(geom, containing_face)
+  VALUES ('POINT(35 28)', 0); -- N25
+INSERT INTO city_data.node(geom, containing_face)
+  VALUES ('POINT(39 28)', 0); -- N26
+INSERT INTO newedge SELECT 17, topology.st_addedgenewfaces('city_data',
+  25, 26,  'LINESTRING(35 28, 39 28)');
+SELECT 'T17', 'E'||edge_id, next_left_edge, next_right_edge,
+  left_face, right_face FROM
+  city_data.edge WHERE edge_id IN (
+  ( SELECT edge_id FROM newedge WHERE id = 17 ) )
+  ORDER BY edge_id;
+SELECT 'N' || node_id, containing_face
+  FROM city_data.node WHERE node_id IN ( 25, 26 );
+
+---------------------------------------------------------------------
+-- Check new relations and faces status
+---------------------------------------------------------------------
+
+SELECT id, array_agg(comp) FROM (
+SELECT f.id, r.element_type||':'||r.element_id as comp
+  FROM city_data.fp f, city_data.relation r
+  WHERE r.topogeo_id = id(f.g) AND r.layer_id = layer_id(f.g)
+  ORDER BY f.id, element_type, element_id
+) f GROUP BY id;
+
+SELECT id, array_agg(comp) FROM (
+SELECT f.id, r.element_type||':'||r.element_id as comp
+  FROM city_data.fc f, city_data.relation r
+  WHERE r.topogeo_id = id(f.g) AND r.layer_id = layer_id(f.g)
+  ORDER BY f.id, element_type, element_id
+) f GROUP BY id;
+
+SELECT 'F'||face_id, st_astext(mbr) FROM city_data.face ORDER BY face_id;
+
+---------------------------------------------------------------------
+-- Cleanups
+---------------------------------------------------------------------
 
+DROP TABLE newedge;
 SELECT topology.DropTopology('city_data');
index f19330af37b23e0ce84ecdfc4616853d862551bc..5ccea63a9ae565276002680852d401c19e990988 100644 (file)
@@ -14,4 +14,95 @@ ERROR:  SQL/MM Spatial exception - coincident edge
 ERROR:  SQL/MM Spatial exception - geometry crosses an edge
 ERROR:  SQL/MM Spatial exception - geometry crosses an edge
 ERROR:  Spatial exception - geometry intersects edge 4
+L1
+L2
+T1|E7|8|-19|0|10
+T1|E10|-20|17|7|11
+T1|E17|-27|11|11|5
+T1|E19|-6|27|3|10
+T1|E27|-7|-10|10|11
+T2|E8|-15|-28|0|12
+T2|E11|28|-18|13|8
+T2|E15|-8|-16|12|0
+T2|E17|-27|11|11|13
+T2|E28|-17|15|13|12
+T3|E11|28|-18|13|15
+T3|E14|16|-13|14|0
+T3|E16|29|-14|14|0
+T3|E18|10|-29|7|15
+T3|E29|14|-11|14|15
+T4|E10|-20|17|17|11
+T4|E13|18|-12|16|0
+T4|E18|-30|-29|16|15
+T4|E20|-9|30|6|17
+T4|E30|10|13|17|16
+T5|E9|19|-22|3|18
+T5|E12|-31|22|18|0
+T5|E20|31|30|19|17
+T5|E31|20|-9|19|18
+T6|E9|19|-32|3|20
+T6|E12|-31|22|20|0
+T6|E22|21|32|0|21
+T6|E32|-22|12|21|20
+T7|E6|7|-33|0|23
+T7|E19|33|27|22|10
+T7|E21|6|9|0|22
+T7|E33|-21|-6|22|23
+T8|E9|-34|-32|25|20
+T8|E19|33|27|24|10
+T8|E21|6|34|0|24
+T8|E34|19|9|24|25
+T9|E12|-31|22|20|0
+T9|E13|18|-35|16|0
+T9|E35|35|-12|26|0
+T10|E13|18|-35|16|0
+T10|E14|16|36|14|0
+T10|E36|-13|-36|0|27
+T11|E21|6|34|0|24
+T11|E22|37|32|0|21
+T11|E37|21|-37|0|28
+T12|E1|1|-38|1|0
+T12|E38|38|-1|29|0
+T13|E2|3|39|2|0
+T13|E39|-2|-39|0|30
+T14|E1|-40|-38|1|0
+T14|E40|1|40|1|1
+N23|
+T15|E1|-40|41|1|0
+T15|E38|38|-1|29|0
+T15|E41|-41|-38|0|0
+N24|
+T16|E2|3|39|31|0
+T16|E3|-3|2|31|31
+T16|E42|42|-42|31|32
+N4|
+T17|E43|-43|43|0|0
+N25|
+N26|
+F3,F4|{3:10,3:11,3:23,3:24,3:25}
+F5,N4|{1:4,3:12,3:13}
+F0|
+F1|
+F9|
+F10|POLYGON((21 14,21 22,35 22,35 14,21 14))
+F11|POLYGON((21 14,21 22,35 22,35 14,21 14))
+F12|POLYGON((35 14,35 22,47 22,47 14,35 14))
+F13|POLYGON((35 14,35 22,47 22,47 14,35 14))
+F14|POLYGON((35 6,35 14,47 14,47 6,35 6))
+F15|POLYGON((35 6,35 14,47 14,47 6,35 6))
+F16|POLYGON((21 6,21 14,35 14,35 6,21 6))
+F17|POLYGON((21 6,21 14,35 14,35 6,21 6))
+F19|POLYGON((19 6,19 14,21 14,21 6,19 6))
+F20|POLYGON((9 6,9 14,21 14,21 6,9 6))
+F21|POLYGON((9 6,9 14,11 14,11 6,9 6))
+F23|POLYGON((9 20,9 22,21 22,21 20,9 20))
+F24|POLYGON((9 14,9 22,21 22,21 14,9 14))
+F25|POLYGON((9 14,9 16,21 16,21 14,9 14))
+F26|POLYGON((18 0,18 6,24 6,24 0,18 0))
+F27|POLYGON((32 0,32 6,38 6,38 0,32 0))
+F28|POLYGON((3 11,3 17,9 17,9 11,3 11))
+F29|POLYGON((5 27,5 30,11 30,11 27,5 27))
+F30|POLYGON((22 27,22 30,28 30,28 27,22 27))
+F31|POLYGON((17 30,17 40,31 40,31 30,17 30))
+F32|POLYGON((20 34,20 37,23 37,23 34,20 34))
 Topology 'city_data' dropped