From: Sandro Santilli Date: Fri, 27 May 2011 14:01:14 +0000 (+0000) Subject: Implement topology.ST_AddEdgeModFace. Includes regress test. [RT-SIGTA] X-Git-Tag: 2.0.0alpha1~1541 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=3ead9a536d291ffce8e9a3e2b2d45bb8d5af2a49;p=postgis Implement topology.ST_AddEdgeModFace. Includes regress test. [RT-SIGTA] git-svn-id: http://svn.osgeo.org/postgis/trunk@7273 b70326c6-7e19-0410-871a-916f4a2858ee --- diff --git a/topology/sql/sqlmm.sql b/topology/sql/sqlmm.sql index b63fd33d9..80585749e 100644 --- a/topology/sql/sqlmm.sql +++ b/topology/sql/sqlmm.sql @@ -2508,6 +2508,655 @@ $$ LANGUAGE 'plpgsql' VOLATILE; --} ST_AddEdgeNewFaces +--{ +-- Topo-Geo and Topo-Net 3: Routine Details +-- X.3.13 +-- +-- ST_AddEdgeModFace(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_AddEdgeModFace(atopology varchar, anode integer, anothernode integer, acurve geometry) + RETURNS INTEGER AS +$$ +DECLARE + rec RECORD; + rrec RECORD; + i INTEGER; + topoid INTEGER; + az FLOAT8; + 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; + do_add BOOLEAN; + bounds GEOMETRY; + p1 GEOMETRY; + p2 GEOMETRY; +BEGIN + + -- + -- All args required + -- + IF atopology IS NULL + OR anode IS NULL + OR anothernode IS NULL + OR acurve IS NULL + THEN + RAISE EXCEPTION 'SQL/MM Spatial exception - null argument'; + END IF; + + -- + -- Acurve must be a LINESTRING + -- + IF substring(geometrytype(acurve), 1, 4) != 'LINE' + THEN + RAISE EXCEPTION 'SQL/MM Spatial exception - invalid curve'; + END IF; + + -- + -- Curve must be simple + -- + IF NOT ST_IsSimple(acurve) THEN + 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 + ST_RemoveRepeatedPoints(acurve) as cleangeom -- convenience + INTO newedge; + + -- 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(newedge.cleangeom), + ST_PointN(newedge.cleangeom, 2)) AS myaz + INTO span; + IF span.myaz IS NULL THEN + RAISE EXCEPTION 'Invalid edge (no two distinct vertices exist)'; + END IF; + + -- 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(newedge.cleangeom), + ST_PointN(newedge.cleangeom, + ST_NumPoints(newedge.cleangeom)-1)) AS myaz + INTO epan; + IF epan.myaz IS NULL THEN + RAISE EXCEPTION 'Invalid edge (no two distinct vertices exist)'; + END IF; + + + -- + -- Check endpoints existance, match with Curve geometry + -- and get face information (if any) + -- + 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, 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 + RAISE EXCEPTION + 'SQL/MM Spatial exception - start node not geometry start point.'; + END IF; + ELSE + IF NOT Equals(rec.geom, ST_EndPoint(acurve)) + THEN + RAISE EXCEPTION + 'SQL/MM Spatial exception - end node not geometry end point.'; + END IF; + END IF; + + i := i + 1; + END LOOP; + + IF NOT newedge.isclosed THEN + IF i < 2 THEN + RAISE EXCEPTION + 'SQL/MM Spatial exception - non-existent node'; + END IF; + ELSE + IF i < 1 THEN + RAISE EXCEPTION + 'SQL/MM Spatial exception - non-existent node'; + END IF; + END IF; + + -- + -- Check if this geometry crosses any node + -- + 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' + LOOP + IF ST_RelateMatch(rec.relate, 'T********') THEN + RAISE EXCEPTION 'SQL/MM Spatial exception - geometry crosses a node'; + END IF; + END LOOP; + + -- + -- Check if this geometry has any interaction with any existing edge + -- + FOR rec IN EXECUTE 'SELECT edge_id, ST_Relate(geom,' + || quote_literal(acurve::text) + || '::geometry, 2) as im FROM ' + || quote_ident(atopology) + || '.edge_data WHERE geom && ' + || quote_literal(acurve::text) || '::geometry' + LOOP + + --RAISE DEBUG 'IM=%',rec.im; + + IF ST_RelateMatch(rec.im, 'F********') THEN + CONTINUE; -- no interior intersection + END IF; + + IF ST_RelateMatch(rec.im, '1FFF*FFF2') THEN + RAISE EXCEPTION + 'SQL/MM Spatial exception - coincident edge'; + END IF; + + -- NOT IN THE SPECS: geometry touches an edge + IF ST_RelateMatch(rec.im, '1********') THEN + RAISE EXCEPTION + 'Spatial exception - geometry intersects edge %', rec.edge_id; + END IF; + + IF ST_RelateMatch(rec.im, 'T********') THEN + RAISE EXCEPTION + 'SQL/MM Spatial exception - geometry crosses an edge'; + END IF; + + END LOOP; + + --------------------------------------------------------------- + -- + -- All checks passed, time to prepare the new edge + -- + --------------------------------------------------------------- + + EXECUTE 'SELECT nextval(' || quote_literal( + quote_ident(atopology) || '.edge_data_edge_id_seq') || ')' + INTO STRICT newedge.edge_id; + + + -- 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, ' + || 'ST_RemoveRepeatedPoints(geom) as geom FROM ' + || quote_ident(atopology) + || '.edge_data WHERE start_node = ' || anode + || ' UNION SELECT edge_id, end_node, -1, left_face, right_face, ' + || 'ST_RemoveRepeatedPoints(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.cleangeom::text); + END IF; + i := 0; + FOR rec IN EXECUTE sql + LOOP -- incident edges { + + 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_StartPoint(rec.geom), 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(rec.geom), + ST_PointN(rec.geom, ST_NumPoints(rec.geom)-1)); + rec.edge_id := -rec.edge_id; + + END IF; + + IF az IS NULL THEN + RAISE EXCEPTION 'Invalid edge % found (no two distinct nodes exist)', + rec.edge_id; + END IF; + + 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 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, ' + || 'ST_RemoveRepeatedPoints(geom) as geom FROM ' + || quote_ident(atopology) + || '.edge_data WHERE start_node = ' || anothernode + || 'UNION SELECT edge_id, end_node, -1, left_face, right_face, ' + || 'ST_RemoveRepeatedPoints(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.cleangeom::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_StartPoint(rec.geom), + 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(rec.geom), + ST_PointN(rec.geom, ST_NumPoints(rec.geom)-1)); + rec.edge_id := -rec.edge_id; + + END IF; + + 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 + -- No splits, all done + RETURN newedge.edge_id; + END IF; + + RAISE NOTICE 'ST_AddEdgeModFace: edge % splitted face %', + newedge.edge_id, newedge.left_face; + + -- Call topology.AddFace for every face containing the new edge + p1 = ST_StartPoint(newedge.cleangeom); + p2 = ST_PointN(newedge.cleangeom, 2); + FOR rec IN SELECT geom FROM ST_Dump(fan.post) + WHERE ST_Contains( + ST_Boundary(geom), + ST_MakeLine(p1, p2) + ) + LOOP -- { + + -- NOTE: the only difference with ST_AddEdgeNewFace here is + -- that we want to retain the face on the right side of + -- the new edge + -- + IF newedge.left_face != 0 THEN -- { + + RAISE NOTICE 'Checking face %', ST_AsText(rec.geom); + + -- Skip this if our edge is on the right side + IF ST_IsEmpty(ST_GeometryN( + ST_SharedPaths(ST_Boundary(ST_ForceRHR(rec.geom)), + ST_MakeLine(p1, p2)), 2)) + THEN + -- We keep this face, but update its MBR + sql := 'UPDATE ' || quote_ident(atopology) + || '.face set mbr = ' || quote_literal(ST_Envelope(rec.geom)::text) + || ' WHERE face_id = ' || newedge.left_face; + EXECUTE sql; + CONTINUE; + END IF; + + END IF; -- } + + RAISE NOTICE 'Adding face %', ST_AsText(rec.geom); + sql := + 'SELECT topology.AddFace(' || quote_literal(atopology) + || ', ' || quote_literal(rec.geom::text) || ', true)'; + EXECUTE sql INTO newface; + + END LOOP; --} + RAISE DEBUG 'Added face: %', newface; + + IF newedge.left_face != 0 THEN -- { + + -- NOT IN THE SPECS: + -- update TopoGeometry compositions to add newface + sql := 'SELECT r.topogeo_id, r.layer_id FROM ' + || quote_ident(atopology) + || '.relation r, 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 '; + --RAISE DEBUG 'SQL: %', sql; + FOR rec IN EXECUTE sql + LOOP + RAISE DEBUG 'TopoGeometry % in layer % contained the face being split (%) - updating to contain also new face %', rec.topogeo_id, rec.layer_id, newedge.left_face, newface; + + -- Add reference to the other face + sql := 'INSERT INTO ' || quote_ident(atopology) + || '.relation VALUES( ' || rec.topogeo_id + || ',' || rec.layer_id || ',' || newface || ', 3)'; + --RAISE DEBUG 'SQL: %', sql; + EXECUTE sql; + + END LOOP; + + END IF; -- } + + RETURN newedge.edge_id; +END +$$ +LANGUAGE 'plpgsql' VOLATILE; +--} ST_AddEdgeModFace + --{ -- Topo-Geo and Topo-Net 3: Routine Details -- X.3.17 diff --git a/topology/test/Makefile b/topology/test/Makefile index b5ce7eb1f..b2f5d4caf 100644 --- a/topology/test/Makefile +++ b/topology/test/Makefile @@ -30,6 +30,7 @@ TESTS = regress/legacy_validate.sql regress/legacy_predicate.sql \ regress/polygonize.sql \ regress/st_addisoedge.sql \ regress/st_addisonode.sql \ + regress/st_addedgemodface.sql \ regress/st_addedgenewfaces.sql \ regress/st_changeedgegeom.sql \ regress/st_getfacegeometry.sql \ diff --git a/topology/test/regress/st_addedgemodface.sql b/topology/test/regress/st_addedgemodface.sql new file mode 100644 index 000000000..5c72c0436 --- /dev/null +++ b/topology/test/regress/st_addedgemodface.sql @@ -0,0 +1,394 @@ +set client_min_messages to ERROR; + +\i load_topology.sql + +-- Endpoint / node mismatch +SELECT topology.ST_AddEdgeModFace('city_data', 7, 6, + 'LINESTRING(36 38,57 33)'); +SELECT topology.ST_AddEdgeModFace('city_data', 5, 7, + 'LINESTRING(36 38,57 33)'); + +-- Crosses a node +SELECT topology.ST_AddEdgeModFace('city_data', 5, 6, + 'LINESTRING(36 38, 41 40, 57 33)'); + +-- Non-existent node +SELECT topology.ST_AddEdgeModFace('city_data', 5, 60000, + 'LINESTRING(36 38,57 33)'); +SELECT topology.ST_AddEdgeModFace('city_data', 60000, 6, + 'LINESTRING(36 38,57 33)'); + +-- Non-simple curve +SELECT topology.ST_AddEdgeModFace('city_data', 5, 5, + 'LINESTRING(36 38, 40 50, 36 38)'); + +-- Collapsed curve +SELECT topology.ST_AddEdgeModFace('city_data', 5, 5, + 'LINESTRING(36 38, 36 38, 36 38)'); + +-- Empty curve +SELECT topology.ST_AddEdgeModFace('city_data', 5, 5, + 'LINESTRING EMPTY'); + +-- Coincident edge +SELECT topology.ST_AddEdgeModFace('city_data', 18, 19, + 'LINESTRING(35 22,47 22)'); + +-- Crosses an edge +SELECT topology.ST_AddEdgeModFace('city_data', 5, 6, + 'LINESTRING(36 38, 40 50, 57 33)'); + +-- Touches an existing edge +SELECT 'O', topology.ST_AddEdgeModFace('city_data', 5, 6, + 'LINESTRING(36 38,45 32,57 33)'); + +-- Shares a portion of an existing edge +SELECT 'O', topology.ST_AddEdgeModFace('city_data', 5, 6, + 'LINESTRING(36 38,38 35,57 33)'); + +--------------------------------------------------------------------- +-- 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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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_addedgemodface('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 ); + +-- +-- New face in universal face, enclosing isolated edge chain +-- +INSERT INTO newedge SELECT 18, topology.st_addedgemodface('city_data', + 25, 26, 'LINESTRING(35 28, 35 45, 63 45, 63 25, 39 25, 39 28)'); +SELECT 'T18', 'E'||edge_id, next_left_edge, next_right_edge, + left_face, right_face FROM + city_data.edge WHERE edge_id IN ( 4, 5, 43, + ( SELECT edge_id FROM newedge WHERE id = 18 ) ) + ORDER BY edge_id; + +-- +-- New face in universal face, with both endpoints on same existing edge +-- +INSERT INTO newedge SELECT 19, topology.st_addedgemodface('city_data', + 9, 8, 'LINESTRING(21 6, 12 0, 9 6)'); +SELECT 'T19', 'E'||edge_id, next_left_edge, next_right_edge, + left_face, right_face FROM + city_data.edge WHERE edge_id IN ( 12, 35, 22, + ( SELECT edge_id FROM newedge WHERE id = 19 ) ) + ORDER BY edge_id; + +-- +-- New face in universal face, with both endpoints on same existing edge +-- and endpoints duplicated +-- +INSERT INTO newedge SELECT 20, topology.st_addedgemodface('city_data', + 10, 11, 'LINESTRING(35 6, 35 6, 44 0, 47 6, 47 6)'); +SELECT 'T20', 'E'||edge_id, next_left_edge, next_right_edge, + left_face, right_face FROM + city_data.edge WHERE edge_id IN ( 36, 14, 16, + ( SELECT edge_id FROM newedge WHERE id = 20 ) ) + ORDER BY edge_id; + +-- +-- Another face in universal face, with both endpoints on same existing edge +-- and both edges' endpoints duplicated +-- +INSERT INTO newedge SELECT 21, topology.st_addedgemodface('city_data', + 10, 11, 'LINESTRING(35 6, 35 6, 44 -4, 47 6, 47 6)'); +SELECT 'T21', '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 IN (20, 21) + UNION VALUES (36),(16) ) + ORDER BY edge_id; + +-- +-- Split a face containing an hole +-- +INSERT INTO newedge SELECT 22, topology.st_addedgemodface('city_data', + 3, 3, 'LINESTRING(25 35, 27 35, 26 34, 25 35)'); +SELECT 'T22', '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 IN (22, 16) + UNION VALUES (2),(3) ) + ORDER BY edge_id; + +-- +-- Split a face containing an holes in both sides of the split +-- +INSERT INTO newedge SELECT 23, topology.st_addedgemodface('city_data', + 2, 3, 'LINESTRING(25 30, 29 32, 29 37, 25 35)'); +SELECT 'T23', '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 IN (13, 23, 22, 16) + UNION VALUES (2),(3) ) + ORDER BY edge_id; + + +--------------------------------------------------------------------- +-- 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'); + diff --git a/topology/test/regress/st_addedgemodface_expected b/topology/test/regress/st_addedgemodface_expected new file mode 100644 index 000000000..a66439c69 --- /dev/null +++ b/topology/test/regress/st_addedgemodface_expected @@ -0,0 +1,142 @@ +BEGIN +t +9 +22 +26 +COMMIT +ERROR: SQL/MM Spatial exception - start node not geometry start point. +ERROR: SQL/MM Spatial exception - end node not geometry end point. +ERROR: SQL/MM Spatial exception - geometry crosses a node +ERROR: SQL/MM Spatial exception - non-existent node +ERROR: SQL/MM Spatial exception - non-existent node +ERROR: SQL/MM Spatial exception - curve not simple +ERROR: Invalid edge (no two distinct vertices exist) +ERROR: Invalid edge (no two distinct vertices exist) +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|4 +T1|E17|-27|11|4|5 +T1|E19|-6|27|3|10 +T1|E27|-7|-10|10|4 +T2|E8|-15|-28|0|5 +T2|E11|28|-18|11|8 +T2|E15|-8|-16|5|0 +T2|E17|-27|11|4|11 +T2|E28|-17|15|11|5 +T3|E11|28|-18|11|8 +T3|E14|16|-13|12|0 +T3|E16|29|-14|12|0 +T3|E18|10|-29|7|8 +T3|E29|14|-11|12|8 +T4|E10|-20|17|13|4 +T4|E13|18|-12|7|0 +T4|E18|-30|-29|7|8 +T4|E20|-9|30|6|13 +T4|E30|10|13|13|7 +T5|E9|19|-22|3|6 +T5|E12|-31|22|6|0 +T5|E20|31|30|14|13 +T5|E31|20|-9|14|6 +T6|E9|19|-32|3|6 +T6|E12|-31|22|6|0 +T6|E22|21|32|0|15 +T6|E32|-22|12|15|6 +T7|E6|7|-33|0|3 +T7|E19|33|27|16|10 +T7|E21|6|9|0|16 +T7|E33|-21|-6|16|3 +T8|E9|-34|-32|16|6 +T8|E19|33|27|17|10 +T8|E21|6|34|0|17 +T8|E34|19|9|17|16 +T9|E12|-31|22|6|0 +T9|E13|18|-35|7|0 +T9|E35|35|-12|18|0 +T10|E13|18|-35|7|0 +T10|E14|16|36|12|0 +T10|E36|-13|-36|0|19 +T11|E21|6|34|0|17 +T11|E22|37|32|0|15 +T11|E37|21|-37|0|20 +T12|E1|1|-38|1|0 +T12|E38|38|-1|21|0 +T13|E2|3|39|2|0 +T13|E39|-2|-39|0|22 +T14|E1|-40|-38|1|0 +T14|E40|1|40|1|1 +N23| +T15|E1|-40|41|1|0 +T15|E38|38|-1|21|0 +T15|E41|-41|-38|0|0 +N24| +T16|E2|3|39|23|0 +T16|E3|-3|2|23|23 +T16|E42|42|-42|23|2 +N4| +T17|E43|-43|43|0|0 +N25| +N26| +T18|E4|-5|4|24|24 +T18|E5|-4|5|24|24 +T18|E43|-44|44|24|0 +T18|E44|-43|43|0|24 +T19|E12|-31|-45|6|25 +T19|E22|37|32|0|15 +T19|E35|35|45|18|0 +T19|E45|22|-12|0|25 +T20|E14|16|46|12|26 +T20|E16|29|-46|12|0 +T20|E36|-13|-36|0|19 +T20|E46|-14|36|26|0 +T21|E16|29|-47|12|0 +T21|E36|-13|-36|0|19 +T21|E46|-14|47|26|27 +T21|E47|-46|36|27|0 +T22|E2|3|39|28|0 +T22|E3|48|2|28|28 +T22|E42|42|-42|28|2 +T22|E48|-3|-48|28|23 +T23|E2|3|39|28|0 +T23|E3|-49|49|28|29 +T23|E39|-2|-39|0|22 +T23|E42|42|-42|28|2 +T23|E48|-3|-48|29|23 +T23|E49|48|2|29|28 +F3,F4|{3:3,3:4,3:10,3:16,3:17} +F5,N4|{1:4,3:5,3:11} +F0| +F1|POLYGON((3 30,3 38,16 38,16 30,3 30)) +F2|POLYGON((20 34,20 37,23 37,23 34,20 34)) +F3|POLYGON((9 20,9 22,21 22,21 20,9 20)) +F4|POLYGON((21 14,21 22,35 22,35 14,21 14)) +F5|POLYGON((35 14,35 22,47 22,47 14,35 14)) +F6|POLYGON((9 6,9 14,21 14,21 6,9 6)) +F7|POLYGON((21 6,21 14,35 14,35 6,21 6)) +F8|POLYGON((35 6,35 14,47 14,47 6,35 6)) +F9|POLYGON((4 31,4 34,7 34,7 31,4 31)) +F10|POLYGON((21 14,21 22,35 22,35 14,21 14)) +F11|POLYGON((35 14,35 22,47 22,47 14,35 14)) +F12|POLYGON((35 6,35 14,47 14,47 6,35 6)) +F13|POLYGON((21 6,21 14,35 14,35 6,21 6)) +F14|POLYGON((19 6,19 14,21 14,21 6,19 6)) +F15|POLYGON((9 6,9 14,11 14,11 6,9 6)) +F16|POLYGON((9 14,9 16,21 16,21 14,9 14)) +F17|POLYGON((9 14,9 22,21 22,21 14,9 14)) +F18|POLYGON((18 0,18 6,24 6,24 0,18 0)) +F19|POLYGON((32 0,32 6,38 6,38 0,32 0)) +F20|POLYGON((3 11,3 17,9 17,9 11,3 11)) +F21|POLYGON((5 27,5 30,11 30,11 27,5 27)) +F22|POLYGON((22 27,22 30,28 30,28 27,22 27)) +F23|POLYGON((25 34,25 35,27 35,27 34,25 34)) +F24|POLYGON((35 25,35 45,63 45,63 25,35 25)) +F25|POLYGON((9 0,9 6,21 6,21 0,9 0)) +F26|POLYGON((35 0,35 6,47 6,47 0,35 0)) +F27|POLYGON((35 -4,35 6,47 6,47 -4,35 -4)) +F28|POLYGON((17 30,17 40,31 40,31 30,17 30)) +F29|POLYGON((25 30,25 37,29 37,29 30,25 30)) +Topology 'city_data' dropped