]> granicus.if.org Git - postgis/commitdiff
Implement, document, and regress-test SQL/MM ST_NewEdgeHeal [RT-SIGTA]
authorSandro Santilli <strk@keybit.net>
Mon, 9 May 2011 15:16:30 +0000 (15:16 +0000)
committerSandro Santilli <strk@keybit.net>
Mon, 9 May 2011 15:16:30 +0000 (15:16 +0000)
git-svn-id: http://svn.osgeo.org/postgis/trunk@7118 b70326c6-7e19-0410-871a-916f4a2858ee

doc/extras_topology.xml
topology/sql/sqlmm.sql
topology/test/Makefile
topology/test/regress/st_newedgeheal.sql [new file with mode: 0644]
topology/test/regress/st_newedgeheal_expected [new file with mode: 0644]

index 7300c64c8119b68c5ed8f5f33dd2060952cb2e6f..a5d2ab01e5eb1f12fc9bbddbad40f3d42b5a0a43 100644 (file)
@@ -1653,6 +1653,7 @@ SELECT topology.ST_ModEdgeSplit('ma_topo',  3, ST_SetSRID(ST_Point(227594,893910
                                <para>
                                <xref linkend="ST_NewEdgesSplit"/>
                                <xref linkend="ST_ModEdgeHeal"/>
+                               <xref linkend="ST_NewEdgeHeal"/>
                                <xref linkend="AddEdge"/>
                                </para>
                        </refsection>
@@ -1705,7 +1706,58 @@ Updates all existing joined edges and relationships accordingly.
                                </para>
                        </refsection>
                </refentry>
+
+               <refentry id="ST_NewEdgeHeal">
+                       <refnamediv>
+                               <refname>ST_NewEdgeHeal</refname>
+                       
+                               <refpurpose>
+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.
+                               </refpurpose>
+                       </refnamediv>
                
+                       <refsynopsisdiv>
+                               <funcsynopsis>
+                                       <funcprototype>
+                                       <funcdef>text <function>ST_NewEdgeHeal</function></funcdef>
+                                       <paramdef><type>varchar </type> <parameter>atopology</parameter></paramdef>
+                                       <paramdef><type>integer </type> <parameter>anedge</parameter></paramdef>
+                                       <paramdef><type>integer </type> <parameter>anotheredge</parameter></paramdef>
+                                       </funcprototype>
+                               </funcsynopsis>
+                       </refsynopsisdiv>
+               
+                       <refsection>
+                <title>Description</title>
+            
+                <para>
+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.
+               </para>
+              
+                       
+                <!-- use this format if new function -->
+                <para>Availability: 2.0</para>
+                <para>&sqlmm_compliant; SQL-MM: Topo-Geo and Topo-Net 3: Routine Details:  X.3.9</para>
+                       </refsection>
+               
+               
+                       <!-- Optionally add a "See Also" section -->
+                       <refsection>
+                               <title>See Also</title>
+                               <para>
+                               <xref linkend="ST_ModEdgeHeal"/>
+                               <xref linkend="ST_ModEdgeSplit"/>
+                               <xref linkend="ST_NewEdgesSplit"/>
+                               </para>
+                       </refsection>
+               </refentry>
+
                <refentry id="ST_MoveIsoNode">
                        <refnamediv>
                                <refname>ST_MoveIsoNode</refname>
@@ -1819,6 +1871,7 @@ SELECT topology.ST_NewEdgesSplit('ma_topo', 2,  ST_GeomFromText('POINT(227578.5
                                <para>
                                <xref linkend="ST_ModEdgeSplit"/>
                                <xref linkend="ST_ModEdgeHeal"/>
+                               <xref linkend="ST_NewEdgeHeal"/>
                                <xref linkend="AddEdge"/>
                                </para>
                        </refsection>
index abe09b81fc9154191262237bf3cf1486ee79303c..8b59eec53e25649f497fd897d5ae97f04e6c3d71 100644 (file)
@@ -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
index c3bc7f40c5bbd2f130ce96b5928e1625bb1effb6..1b082d984cc0a8e7f684d1d82891817ea76534e1 100644 (file)
@@ -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 (file)
index 0000000..641a347
--- /dev/null
@@ -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 (file)
index 0000000..7e9ccc3
--- /dev/null
@@ -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