From: Sandro Santilli Date: Mon, 9 May 2011 15:16:30 +0000 (+0000) Subject: Implement, document, and regress-test SQL/MM ST_NewEdgeHeal [RT-SIGTA] X-Git-Tag: 2.0.0alpha1~1690 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=de679b25e78aaa6c3377b2fe9e3896c448fe8e1f;p=postgis Implement, document, and regress-test SQL/MM ST_NewEdgeHeal [RT-SIGTA] git-svn-id: http://svn.osgeo.org/postgis/trunk@7118 b70326c6-7e19-0410-871a-916f4a2858ee --- diff --git a/doc/extras_topology.xml b/doc/extras_topology.xml index 7300c64c8..a5d2ab01e 100644 --- a/doc/extras_topology.xml +++ b/doc/extras_topology.xml @@ -1653,6 +1653,7 @@ SELECT topology.ST_ModEdgeSplit('ma_topo', 3, ST_SetSRID(ST_Point(227594,893910 + @@ -1705,7 +1706,58 @@ Updates all existing joined edges and relationships accordingly. + + + + ST_NewEdgeHeal + + +Heal two edges by deleting the node connecting them, deleting both edges, +and replacing them with an edge whose direction is the same as the first +edge provided. + + + + + + text ST_NewEdgeHeal + varchar atopology + integer anedge + integer anotheredge + + + + + + Description + + +Heal two edges by deleting the node connecting them, deleting both edges, +and replacing them with an edge whose direction is the same as the first +edge provided. +Returns the id of the new edge replacing the healed ones. +Updates all existing joined edges and relationships accordingly. + + + + + Availability: 2.0 + &sqlmm_compliant; SQL-MM: Topo-Geo and Topo-Net 3: Routine Details: X.3.9 + + + + + + See Also + + + + + + + + ST_MoveIsoNode @@ -1819,6 +1871,7 @@ SELECT topology.ST_NewEdgesSplit('ma_topo', 2, ST_GeomFromText('POINT(227578.5 + diff --git a/topology/sql/sqlmm.sql b/topology/sql/sqlmm.sql index abe09b81f..8b59eec53 100644 --- a/topology/sql/sqlmm.sql +++ b/topology/sql/sqlmm.sql @@ -106,6 +106,284 @@ $$ LANGUAGE 'plpgsql' VOLATILE; --} ST_GetFaceEdges +--{ +-- Topo-Geo and Topo-Net 3: Routine Details +-- X.3.10 +-- +-- ST_NewEdgeHeal(atopology, anedge, anotheredge) +-- +-- Not in the specs: +-- * Refuses to heal two edges if any of the two is closed +-- * Raise an exception when trying to heal an edge with itself +-- * Raise an exception if any TopoGeometry is defined by only one +-- of the two edges +-- * Update references in the Relation table. +-- +CREATE OR REPLACE FUNCTION topology.ST_NewEdgeHeal(toponame varchar, e1id integer, e2id integer) + RETURNS int +AS +$$ +DECLARE + e1rec RECORD; + e2rec RECORD; + rec RECORD; + newedgeid int; + commonnode int; + caseno int; + topoid int; + sql text; + e2sign int; + eidary int[]; +BEGIN + -- + -- toponame and face_id are required + -- + IF toponame IS NULL OR e1id IS NULL OR e2id IS NULL THEN + RAISE EXCEPTION 'SQL/MM Spatial exception - null argument'; + END IF; + + -- NOT IN THE SPECS: see if the same edge is given twice.. + IF e1id = e2id THEN + RAISE EXCEPTION 'Cannot heal edge % with itself, try with another', e1id; + END IF; + + -- Get topology id + BEGIN + SELECT id FROM topology.topology + INTO STRICT topoid WHERE name = toponame; + EXCEPTION + WHEN NO_DATA_FOUND THEN + RAISE EXCEPTION 'SQL/MM Spatial exception - invalid topology name'; + END; + + BEGIN + EXECUTE 'SELECT * FROM ' || quote_ident(toponame) + || '.edge_data WHERE edge_id = ' || e1id + INTO STRICT e1rec; + EXCEPTION + WHEN NO_DATA_FOUND THEN + RAISE EXCEPTION 'SQL/MM Spatial exception – non-existent edge %', e1id; + WHEN INVALID_SCHEMA_NAME THEN + RAISE EXCEPTION 'SQL/MM Spatial exception - invalid topology name'; + WHEN UNDEFINED_TABLE THEN + RAISE EXCEPTION 'corrupted topology "%" (missing edge_data table)', + toponame; + END; + + BEGIN + EXECUTE 'SELECT * FROM ' || quote_ident(toponame) + || '.edge_data WHERE edge_id = ' || e2id + INTO STRICT e2rec; + EXCEPTION + WHEN NO_DATA_FOUND THEN + RAISE EXCEPTION 'SQL/MM Spatial exception – non-existent edge %', e2id; + -- NOTE: checks for INVALID_SCHEMA_NAME or UNDEFINED_TABLE done before + END; + + + -- NOT IN THE SPECS: See if any of the two edges are closed. + IF e1rec.start_node = e1rec.end_node THEN + RAISE EXCEPTION 'Edge % is closed, cannot heal to edge %', e1id, e2id; + END IF; + IF e2rec.start_node = e2rec.end_node THEN + RAISE EXCEPTION 'Edge % is closed, cannot heal to edge %', e2id, e1id; + END IF; + + -- Find common node + IF e1rec.end_node = e2rec.start_node THEN + commonnode = e1rec.end_node; + caseno = 1; + ELSIF e1rec.end_node = e2rec.end_node THEN + commonnode = e1rec.end_node; + caseno = 2; + ELSIF e1rec.start_node = e2rec.start_node THEN + commonnode = e1rec.start_node; + caseno = 3; + ELSIF e1rec.start_node = e2rec.end_node THEN + commonnode = e1rec.start_node; + caseno = 4; + ELSE + RAISE EXCEPTION 'SQL/MM Spatial exception – non-connected edges'; + END IF; + + -- Check if any other edge is connected to the common node + FOR rec IN EXECUTE 'SELECT edge_id FROM ' || quote_ident(toponame) + || '.edge_data WHERE ( edge_id != ' || e1id + || ' AND edge_id != ' || e2id || ') AND ( start_node = ' + || commonnode || ' OR end_node = ' || commonnode || ' )' + LOOP + RAISE EXCEPTION + 'SQL/MM Spatial exception – other edges connected (ie: %)', rec.edge_id; + END LOOP; + + -- NOT IN THE SPECS: + -- check if any topo_geom is defined only by one of the + -- input edges. In such case there would be no way to adapt + -- the definition in case of healing, so we'd have to bail out + eidary = ARRAY[e1id, e2id]; + sql := 'SELECT t.* from (' + || 'SELECT r.topogeo_id, r.layer_id' + || ', l.schema_name, l.table_name, l.feature_column' + || ', array_agg(abs(r.element_id)) as elems ' + || 'FROM topology.layer l INNER JOIN ' + || quote_ident(toponame) + || '.relation r ON (l.layer_id = r.layer_id) ' + || 'WHERE l.level = 0 AND l.feature_type = 2 ' + || ' AND l.topology_id = ' || topoid + || ' AND abs(r.element_id) IN (' || e1id || ',' || e2id || ') ' + || 'group by r.topogeo_id, r.layer_id, l.schema_name, l.table_name, ' + || ' l.feature_column ) t WHERE NOT t.elems @> ' + || quote_literal(eidary); + --RAISE DEBUG 'SQL: %', sql; + FOR rec IN EXECUTE sql LOOP + RAISE EXCEPTION 'TopoGeom % in layer % (%.%.%) cannot be represented healing edges % and %', + rec.topogeo_id, rec.layer_id, + rec.schema_name, rec.table_name, rec.feature_column, + e1id, e2id; + END LOOP; + + -- Create new edge { + rec := e1rec; + rec.geom = ST_LineMerge(ST_Collect(e1rec.geom, e2rec.geom)); + IF caseno = 1 THEN -- e1.end = e2.start + IF NOT ST_Equals(ST_StartPoint(rec.geom), ST_StartPoint(e1rec.geom)) THEN + RAISE DEBUG 'caseno=1: LineMerge did not maintain startpoint'; + rec.geom = ST_Reverse(rec.geom); + END IF; + rec.end_node = e2rec.end_node; + rec.next_left_edge = e2rec.next_left_edge; + e2sign = 1; + ELSIF caseno = 2 THEN -- e1.end = e2.end + IF NOT ST_Equals(ST_StartPoint(rec.geom), ST_StartPoint(e1rec.geom)) THEN + RAISE DEBUG 'caseno=2: LineMerge did not maintain startpoint'; + rec.geom = ST_Reverse(rec.geom); + END IF; + rec.end_node = e2rec.start_node; + rec.next_left_edge = e2rec.next_right_edge; + e2sign = -1; + ELSIF caseno = 3 THEN -- e1.start = e2.start + IF NOT ST_Equals(ST_EndPoint(rec.geom), ST_EndPoint(e1rec.geom)) THEN + RAISE DEBUG 'caseno=4: LineMerge did not maintain endpoint'; + rec.geom = ST_Reverse(rec.geom); + END IF; + rec.start_node = e2rec.end_node; + rec.next_right_edge = e2rec.next_left_edge; + e2sign = -1; + ELSIF caseno = 4 THEN -- e1.start = e2.end + IF NOT ST_Equals(ST_EndPoint(rec.geom), ST_EndPoint(e1rec.geom)) THEN + RAISE DEBUG 'caseno=4: LineMerge did not maintain endpoint'; + rec.geom = ST_Reverse(rec.geom); + END IF; + rec.start_node = e2rec.start_node; + rec.next_right_edge = e2rec.next_right_edge; + e2sign = 1; + END IF; + -- } + + -- Insert new edge { + EXECUTE 'SELECT nextval(' || quote_literal( + quote_ident(toponame) || '.edge_data_edge_id_seq' + ) || ')' INTO STRICT newedgeid; + EXECUTE 'INSERT INTO ' || quote_ident(toponame) + || '.edge VALUES(' || newedgeid + || ',' || rec.start_node + || ',' || rec.end_node + || ',' || rec.next_left_edge + || ',' || rec.next_right_edge + || ',' || rec.left_face + || ',' || rec.right_face + || ',' || quote_literal(rec.geom::text) + || ')'; + -- End of new edge insertion } + + -- Update next_left_edge/next_right_edge for + -- any edge having them still pointing at the edges being removed + -- (e2id) + -- + -- NOTE: + -- *(next_XXX_edge/e2id) serves the purpose of extracting existing + -- sign from the value, while *e2sign changes that sign again if we + -- reverted edge2 direction + -- + sql := 'UPDATE ' || quote_ident(toponame) + || '.edge_data SET abs_next_left_edge = ' || newedgeid + || ', next_left_edge = ' || e2sign*newedgeid + || '*(next_left_edge/' + || e2id || ') WHERE abs_next_left_edge = ' || e2id; + --RAISE DEBUG 'SQL: %', sql; + EXECUTE sql; + sql := 'UPDATE ' || quote_ident(toponame) + || '.edge_data SET abs_next_right_edge = ' || newedgeid + || ', next_right_edge = ' || e2sign*newedgeid + || '*(next_right_edge/' + || e2id || ') WHERE abs_next_right_edge = ' || e2id; + --RAISE DEBUG 'SQL: %', sql; + EXECUTE sql; + + -- New edge has the same direction as old edge 1 + sql := 'UPDATE ' || quote_ident(toponame) + || '.edge_data SET abs_next_left_edge = ' || newedgeid + || ', next_left_edge = ' || newedgeid + || '*(next_left_edge/' + || e1id || ') WHERE abs_next_left_edge = ' || e1id; + --RAISE DEBUG 'SQL: %', sql; + EXECUTE sql; + sql := 'UPDATE ' || quote_ident(toponame) + || '.edge_data SET abs_next_right_edge = ' || newedgeid + || ', next_right_edge = ' || newedgeid + || '*(next_right_edge/' + || e1id || ') WHERE abs_next_right_edge = ' || e1id; + --RAISE DEBUG 'SQL: %', sql; + EXECUTE sql; + + -- + -- NOT IN THE SPECS: + -- Replace composition rows involving the two + -- edges as one involving the new edge. + -- It makes a DELETE and an UPDATE to do all + sql := 'DELETE FROM ' || quote_ident(toponame) + || '.relation r USING topology.layer l ' + || 'WHERE l.level = 0 AND l.feature_type = 2' + || ' AND l.topology_id = ' || topoid + || ' AND l.layer_id = r.layer_id AND abs(r.element_id) = ' + || e2id; + --RAISE DEBUG 'SQL: %', sql; + EXECUTE sql; + sql := 'UPDATE ' || quote_ident(toponame) + || '.relation r ' + || ' SET element_id = ' || newedgeid || '*(element_id/' + || e1id + || ') FROM topology.layer l WHERE l.level = 0 AND l.feature_type = 2' + || ' AND l.topology_id = ' || topoid + || ' AND l.layer_id = r.layer_id AND abs(r.element_id) = ' + || e1id + ; + RAISE DEBUG 'SQL: %', sql; + EXECUTE sql; + + + -- Delete both edges + EXECUTE 'DELETE FROM ' || quote_ident(toponame) + || '.edge_data WHERE edge_id = ' || e2id; + EXECUTE 'DELETE FROM ' || quote_ident(toponame) + || '.edge_data WHERE edge_id = ' || e1id; + + -- Delete the common node + BEGIN + EXECUTE 'DELETE FROM ' || quote_ident(toponame) + || '.node WHERE node_id = ' || commonnode; + EXCEPTION + WHEN UNDEFINED_TABLE THEN + RAISE EXCEPTION 'corrupted topology "%" (missing node table)', + toponame; + END; + + RETURN newedgeid; +END +$$ +LANGUAGE 'plpgsql' VOLATILE; +--} ST_NewEdgeHeal + --{ -- Topo-Geo and Topo-Net 3: Routine Details -- X.3.11 diff --git a/topology/test/Makefile b/topology/test/Makefile index c3bc7f40c..1b082d984 100644 --- a/topology/test/Makefile +++ b/topology/test/Makefile @@ -31,6 +31,7 @@ TESTS = regress/legacy_validate.sql regress/legacy_predicate.sql \ regress/st_getfacegeometry.sql \ regress/st_getfaceedges.sql \ regress/st_modedgeheal.sql \ + regress/st_newedgeheal.sql \ regress/topoelement.sql \ regress/topoelementarray_agg.sql \ regress/topo2.5d.sql \ diff --git a/topology/test/regress/st_newedgeheal.sql b/topology/test/regress/st_newedgeheal.sql new file mode 100644 index 000000000..641a347cc --- /dev/null +++ b/topology/test/regress/st_newedgeheal.sql @@ -0,0 +1,120 @@ +\set VERBOSITY terse +set client_min_messages to ERROR; + +-- Import city_data +\i load_topology.sql + +SELECT topology.ST_NewEdgeHeal('city_data', 1, null); +SELECT topology.ST_NewEdgeHeal('city_data', null, 1); +SELECT topology.ST_NewEdgeHeal(null, 1, 2); +SELECT topology.ST_NewEdgeHeal('', 1, 2); + +-- Not connected edges +SELECT topology.ST_NewEdgeHeal('city_data', 25, 3); + +-- Other connected edges +SELECT topology.ST_NewEdgeHeal('city_data', 9, 10); + +-- Closed edge +SELECT topology.ST_NewEdgeHeal('city_data', 2, 3); +SELECT topology.ST_NewEdgeHeal('city_data', 3, 2); + +-- Heal to self +SELECT topology.ST_NewEdgeHeal('city_data', 25, 25); + +-- Good ones { + +-- check state before +SELECT 'E'||edge_id, + ST_AsText(ST_StartPoint(geom)), ST_AsText(ST_EndPoint(geom)), + next_left_edge, next_right_edge, start_node, end_node + FROM city_data.edge_data ORDER BY edge_id; +SELECT 'N'||node_id FROM city_data.node; + +-- No other edges involved, SQL/MM caseno 2, drops node 6 +SELECT 'MH(4,5)', topology.ST_NewEdgeHeal('city_data', 4, 5); + +-- Face and other edges involved, SQL/MM caseno 1, drops node 16 +SELECT 'MH(21,6)', topology.ST_NewEdgeHeal('city_data', 21, 6); +-- Face and other edges involved, SQL/MM caseno 2, drops node 19 +SELECT 'MH(8,15)', topology.ST_NewEdgeHeal('city_data', 8, 15); +-- Face and other edges involved, SQL/MM caseno 3, drops node 8 +SELECT 'MH(12,22)', topology.ST_NewEdgeHeal('city_data', 12, 22); +-- Face and other edges involved, SQL/MM caseno 4, drops node 11 +SELECT 'MH(16,14)', topology.ST_NewEdgeHeal('city_data', 16, 14); + +-- check state after +SELECT 'E'||edge_id, + ST_AsText(ST_StartPoint(geom)), ST_AsText(ST_EndPoint(geom)), + next_left_edge, next_right_edge, start_node, end_node + FROM city_data.edge_data ORDER BY edge_id; +SELECT 'N'||node_id FROM city_data.node; + +-- } + +-- clean up +SELECT topology.DropTopology('city_data'); + +------------------------------------------------------------------------- +------------------------------------------------------------------------- +------------------------------------------------------------------------- + +-- Now test in presence of features + +SELECT topology.CreateTopology('t') > 1; +CREATE TABLE t.f(id varchar); +SELECT topology.AddTopoGeometryColumn('t', 't', 'f','g', 'LINE'); + +SELECT 'E'||topology.AddEdge('t', 'LINESTRING(2 2, 2 8)'); -- 1 +SELECT 'E'||topology.AddEdge('t', 'LINESTRING(2 8, 8 8)'); -- 2 + +INSERT INTO t.f VALUES ('F+E1', + topology.CreateTopoGeom('t', 2, 1, '{{1,2}}')); + +-- This should be forbidden, as F+E1 above could not be +-- defined w/out one of the edges +SELECT topology.ST_NewEdgeHeal('t', 1, 2); +SELECT topology.ST_NewEdgeHeal('t', 2, 1); + +-- This is for ticket #941 +SELECT topology.ST_NewEdgeHeal('t', 1, 200); +SELECT topology.ST_NewEdgeHeal('t', 100, 2); + +-- Now see how signed edges are updated + +SELECT 'E'||topology.AddEdge('t', 'LINESTRING(0 0, 5 0)'); -- 3 +SELECT 'E'||topology.AddEdge('t', 'LINESTRING(10 0, 5 0)'); -- 4 + +INSERT INTO t.f VALUES ('F+E3-E4', + topology.CreateTopoGeom('t', 2, 1, '{{3,2},{-4,2}}')); +INSERT INTO t.f VALUES ('F-E3+E4', + topology.CreateTopoGeom('t', 2, 1, '{{-3,2},{4,2}}')); + +SELECT r.topogeo_id, r.element_id + FROM t.relation r, t.f f WHERE + r.layer_id = layer_id(f.g) AND r.topogeo_id = id(f.g) + AND r.topogeo_id in (2,3) + ORDER BY r.layer_id, r.topogeo_id; + +-- This is fine, but will have to tweak definition of +-- 'F+E3-E4' and 'F-E3+E4' +SELECT 'MH(3,4)', topology.ST_NewEdgeHeal('t', 3, 4); + +-- This is for ticket #942 +SELECT topology.ST_NewEdgeHeal('t', 1, 5); + +SELECT r.topogeo_id, r.element_id + FROM t.relation r, t.f f WHERE + r.layer_id = layer_id(f.g) AND r.topogeo_id = id(f.g) + AND r.topogeo_id in (2,3) + ORDER BY r.layer_id, r.topogeo_id; + +SELECT topology.DropTopology('t'); + +------------------------------------------------------------------------- +------------------------------------------------------------------------- +------------------------------------------------------------------------- + +-- TODO: test registered but unexistent topology +-- TODO: test registered but corrupted topology +-- (missing node, edge, relation...) diff --git a/topology/test/regress/st_newedgeheal_expected b/topology/test/regress/st_newedgeheal_expected new file mode 100644 index 000000000..7e9ccc3d9 --- /dev/null +++ b/topology/test/regress/st_newedgeheal_expected @@ -0,0 +1,122 @@ +BEGIN +t +8 +22 +26 +COMMIT +ERROR: SQL/MM Spatial exception - null argument +ERROR: SQL/MM Spatial exception - null argument +ERROR: SQL/MM Spatial exception - null argument +ERROR: SQL/MM Spatial exception - invalid topology name +ERROR: SQL/MM Spatial exception – non-connected edges +ERROR: SQL/MM Spatial exception – other edges connected (ie: 19) +ERROR: Edge 2 is closed, cannot heal to edge 3 +ERROR: Edge 2 is closed, cannot heal to edge 3 +ERROR: Cannot heal edge 25 with itself, try with another +E1|POINT(8 30)|POINT(8 30)|1|-1|1|1 +E2|POINT(25 30)|POINT(25 30)|-3|-2|2|2 +E3|POINT(25 30)|POINT(25 35)|-3|2|2|3 +E4|POINT(36 38)|POINT(57 33)|-5|4|5|6 +E5|POINT(41 40)|POINT(57 33)|-4|5|7|6 +E6|POINT(9 22)|POINT(21 22)|7|-21|16|17 +E7|POINT(21 22)|POINT(35 22)|8|-19|17|18 +E8|POINT(35 22)|POINT(47 22)|-15|-17|18|19 +E9|POINT(9 14)|POINT(21 14)|19|-22|15|14 +E10|POINT(35 14)|POINT(21 14)|-20|17|13|14 +E11|POINT(35 14)|POINT(47 14)|15|-18|13|12 +E12|POINT(9 6)|POINT(21 6)|20|22|8|9 +E13|POINT(21 6)|POINT(35 6)|18|-12|9|10 +E14|POINT(35 6)|POINT(47 6)|16|-13|10|11 +E15|POINT(47 14)|POINT(47 22)|-8|-16|12|19 +E16|POINT(47 6)|POINT(47 14)|-11|-14|11|12 +E17|POINT(35 14)|POINT(35 22)|-7|11|13|18 +E18|POINT(35 6)|POINT(35 14)|10|14|10|13 +E19|POINT(21 14)|POINT(21 22)|-6|-10|14|17 +E20|POINT(21 6)|POINT(21 14)|-9|13|9|14 +E21|POINT(9 14)|POINT(9 22)|6|9|15|16 +E22|POINT(9 6)|POINT(9 14)|21|12|8|15 +E25|POINT(9 35)|POINT(13 35)|-25|25|21|22 +E26|POINT(4 31)|POINT(4 31)|26|-26|20|20 +N1 +N2 +N3 +N4 +N5 +N6 +N7 +N8 +N9 +N10 +N11 +N12 +N13 +N14 +N15 +N16 +N17 +N18 +N19 +N20 +N21 +N22 +MH(4,5)|27 +MH(21,6)|28 +MH(8,15)|29 +MH(12,22)|30 +MH(16,14)|31 +E1|POINT(8 30)|POINT(8 30)|1|-1|1|1 +E2|POINT(25 30)|POINT(25 30)|-3|-2|2|2 +E3|POINT(25 30)|POINT(25 35)|-3|2|2|3 +E7|POINT(21 22)|POINT(35 22)|29|-19|17|18 +E9|POINT(9 14)|POINT(21 14)|19|30|15|14 +E10|POINT(35 14)|POINT(21 14)|-20|17|13|14 +E11|POINT(35 14)|POINT(47 14)|-29|-18|13|12 +E13|POINT(21 6)|POINT(35 6)|18|-30|9|10 +E17|POINT(35 14)|POINT(35 22)|-7|11|13|18 +E18|POINT(35 6)|POINT(35 14)|10|31|10|13 +E19|POINT(21 14)|POINT(21 22)|-28|-10|14|17 +E20|POINT(21 6)|POINT(21 14)|-9|13|9|14 +E25|POINT(9 35)|POINT(13 35)|-25|25|21|22 +E26|POINT(4 31)|POINT(4 31)|26|-26|20|20 +E27|POINT(36 38)|POINT(41 40)|-27|27|5|7 +E28|POINT(9 14)|POINT(21 22)|7|9|15|17 +E29|POINT(35 22)|POINT(47 14)|-31|-17|18|12 +E30|POINT(9 14)|POINT(21 6)|20|28|15|9 +E31|POINT(35 6)|POINT(47 14)|-11|-13|10|12 +N1 +N2 +N3 +N4 +N5 +N7 +N9 +N10 +N12 +N13 +N14 +N15 +N17 +N18 +N20 +N21 +N22 +Topology 'city_data' dropped +t +1 +E1 +E2 +ERROR: TopoGeom 1 in layer 1 (t.f.g) cannot be represented healing edges 1 and 2 +ERROR: TopoGeom 1 in layer 1 (t.f.g) cannot be represented healing edges 2 and 1 +ERROR: SQL/MM Spatial exception – non-existent edge 200 +ERROR: SQL/MM Spatial exception – non-existent edge 100 +E3 +E4 +2|-4 +2|3 +3|4 +3|-3 +MH(3,4)|5 +ERROR: SQL/MM Spatial exception – non-connected edges +2|5 +3|-5 +Topology 't' dropped