-- X.3.18
--
-- ST_CreateTopoGeo(atopology, acollection)
---
-CREATE OR REPLACE FUNCTION topology.ST_CreateTopoGeo(varchar, geometry)
+--}{
+CREATE OR REPLACE FUNCTION topology.ST_CreateTopoGeo(atopology varchar, acollection geometry)
RETURNS text
AS
$$
DECLARE
- atopology alias for $1;
- acollection alias for $2;
typ char(4);
rec RECORD;
ret int;
- schemaoid oid;
+ nodededges GEOMETRY;
+ points GEOMETRY;
+ snode_id int;
+ enode_id int;
+ tolerance FLOAT8;
+ topoinfo RECORD;
BEGIN
+
IF atopology IS NULL OR acollection IS NULL THEN
RAISE EXCEPTION 'SQL/MM Spatial exception - null argument';
END IF;
- -- Verify existance of the topology schema
- FOR rec in EXECUTE 'SELECT oid FROM pg_namespace WHERE '
- || ' nspname = ' || quote_literal(atopology)
- || ' GROUP BY oid'
-
- LOOP
- schemaoid := rec.oid;
- END LOOP;
+ -- Get topology information
+ BEGIN
+ SELECT * FROM topology.topology
+ INTO STRICT topoinfo WHERE name = atopology;
+ EXCEPTION
+ WHEN NO_DATA_FOUND THEN
+ RAISE EXCEPTION 'SQL/MM Spatial exception - invalid topology name';
+ END;
- IF schemaoid IS NULL THEN
- RAISE EXCEPTION 'SQL/MM Spatial exception - non-existent schema';
+ -- Check SRID compatibility
+ IF ST_SRID(acollection) != topoinfo.SRID THEN
+ RAISE EXCEPTION 'Geometry SRID (%) does not match topology SRID (%)',
+ ST_SRID(acollection), topoinfo.SRID;
END IF;
- -- Verify existance of the topology views in the topology schema
- FOR rec in EXECUTE 'SELECT count(*) FROM pg_class WHERE '
- || ' relnamespace = ' || schemaoid
- || ' and relname = ''node'''
- || ' OR relname = ''edge'''
- || ' OR relname = ''face'''
- LOOP
- IF rec.count < 3 THEN
- RAISE EXCEPTION 'SQL/MM Spatial exception - non-existent view';
- END IF;
- END LOOP;
+ -- Verify pre-conditions (valid, empty topology schema exists)
+ BEGIN -- {
- -- Verify the topology views in the topology schema to be empty
- FOR rec in EXECUTE
- 'SELECT count(*) FROM '
- || quote_ident(atopology) || '.edge_data '
- || ' UNION ' ||
- 'SELECT count(*) FROM '
- || quote_ident(atopology) || '.node '
+ -- Verify the topology views in the topology schema to be empty
+ FOR rec in EXECUTE
+ 'SELECT count(*) FROM '
+ || quote_ident(atopology) || '.edge_data '
+ || ' UNION ' ||
+ 'SELECT count(*) FROM '
+ || quote_ident(atopology) || '.node '
+ LOOP
+ IF rec.count > 0 THEN
+ RAISE EXCEPTION 'SQL/MM Spatial exception - non-empty view';
+ END IF;
+ END LOOP;
+
+ -- face check is separated as it will contain a single (world)
+ -- face record
+ FOR rec in EXECUTE
+ 'SELECT count(*) FROM '
+ || quote_ident(atopology) || '.face '
+ LOOP
+ IF rec.count != 1 THEN
+ RAISE EXCEPTION 'SQL/MM Spatial exception - non-empty face view';
+ END IF;
+ END LOOP;
+
+ EXCEPTION
+ WHEN INVALID_SCHEMA_NAME THEN
+ RAISE EXCEPTION 'SQL/MM Spatial exception - invalid topology name';
+ WHEN UNDEFINED_TABLE THEN
+ RAISE EXCEPTION 'SQL/MM Spatial exception - non-existent view';
+
+ END; -- }
+
+ RAISE DEBUG 'Noding input linework';
+
+ --
+ -- Node input linework with itself
+ --
+ WITH components AS ( SELECT geom FROM ST_Dump(acollection) )
+ SELECT ST_UnaryUnion(ST_Collect(geom)) FROM (
+ SELECT geom FROM components
+ WHERE ST_Dimension(geom) = 1
+ UNION
+ SELECT ST_Boundary(geom) FROM components
+ WHERE ST_Dimension(geom) = 2
+ ) as linework INTO STRICT nodededges;
+
+ RAISE DEBUG 'Computed % noded edges', ST_NumGeometries(nodededges);
+
+ --
+ -- Linemerge the resulting edges, to reduce the working set
+ --
+ SELECT ST_LineMerge(nodededges) INTO STRICT nodededges;
+
+ RAISE DEBUG 'Merged edges: %', ST_NumGeometries(nodededges);
+
+
+ --
+ -- Collect input points
+ --
+ SELECT ST_Union(geom) FROM (
+ SELECT geom FROM ST_Dump(acollection)
+ WHERE ST_Dimension(geom) = 0
+ ) as nodes INTO STRICT points;
+
+ RAISE DEBUG 'Collected % input points', ST_NumGeometries(points);
+
+ --
+ -- Further split edges by points
+ --
+ FOR rec IN SELECT geom FROM ST_Dump(points)
LOOP
- IF rec.count > 0 THEN
- RAISE EXCEPTION 'SQL/MM Spatial exception - non-empty view';
- END IF;
+ -- Use the node to split edges
+ SELECT ST_Union(geom) -- TODO: ST_UnaryUnion ?
+ FROM ST_Dump(ST_Split(nodededges, rec.geom))
+ INTO STRICT nodededges;
END LOOP;
- -- face check is separated as it will contain a single (world)
- -- face record
- FOR rec in EXECUTE
- 'SELECT count(*) FROM '
- || quote_ident(atopology) || '.face '
+ RAISE DEBUG 'Noded edges became % after point-split',
+ ST_NumGeometries(nodededges);
+
+ --
+ -- Collect all nodes (from points and noded linework endpoints)
+ --
+
+ WITH edges AS ( SELECT geom FROM ST_Dump(nodededges) )
+ SELECT ST_Union( -- TODO: ST_UnaryUnion ?
+ COALESCE(ST_UnaryUnion(ST_Collect(geom)),
+ ST_SetSRID('POINT EMPTY'::geometry, topoinfo.SRID)),
+ COALESCE(points,
+ ST_SetSRID('POINT EMPTY'::geometry, topoinfo.SRID))
+ )
+ FROM (
+ SELECT ST_StartPoint(geom) as geom FROM edges
+ UNION
+ SELECT ST_EndPoint(geom) FROM edges
+ ) as endpoints INTO points;
+
+ RAISE DEBUG 'Total nodes count: %', ST_NumGeometries(points);
+
+ --
+ -- Add all nodes as isolated so that
+ -- later calls to AddEdgeModFace will tweak their being
+ -- isolated or not...
+ --
+ FOR rec IN SELECT geom FROM ST_Dump(points)
LOOP
- IF rec.count != 1 THEN
- RAISE EXCEPTION 'SQL/MM Spatial exception - non-empty face view';
- END IF;
+ PERFORM topology.ST_AddIsoNode(atopology, 0, rec.geom);
END LOOP;
+
- --
- -- LOOP through the elements invoking the specific function
- --
- FOR rec IN SELECT geom(ST_Dump(acollection))
+ FOR rec IN SELECT geom FROM ST_Dump(nodededges)
LOOP
- typ := substring(geometrytype(rec.geom), 1, 3);
-
- IF typ = 'LIN' THEN
- SELECT topology.TopoGeo_addLinestring(atopology, rec.geom) INTO ret;
- ELSIF typ = 'POI' THEN
- SELECT topology.TopoGeo_AddPoint(atopology, rec.geom) INTO ret;
- ELSIF typ = 'POL' THEN
- SELECT topology.TopoGeo_AddPolygon(atopology, rec.geom) INTO ret;
- ELSE
- RAISE EXCEPTION 'ST_CreateTopoGeo got unknown geometry type: %', typ;
- END IF;
-
+ SELECT topology.GetNodeByPoint(atopology, st_startpoint(rec.geom), 0)
+ INTO STRICT snode_id;
+ SELECT topology.GetNodeByPoint(atopology, st_endpoint(rec.geom), 0)
+ INTO STRICT enode_id;
+ PERFORM topology.ST_AddEdgeModFace(atopology, snode_id, enode_id, rec.geom);
END LOOP;
RETURN 'Topology ' || atopology || ' populated';
- RAISE EXCEPTION 'ST_CreateTopoGeo not implemente yet';
END
$$
LANGUAGE 'plpgsql' VOLATILE;
--- /dev/null
+\set VERBOSITY terse
+set client_min_messages to ERROR;
+
+INSERT INTO spatial_ref_sys ( auth_name, auth_srid, srid, proj4text ) VALUES ( 'EPSG', 4326, 4326, '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs' );
+
+-- Invalid topologies
+select topology.st_createtopogeo('', 'GEOMETRYCOLLECTION(POINT(0 0))');
+select topology.st_createtopogeo('t', 'GEOMETRYCOLLECTION(POINT(0 0))');
+select topology.st_createtopogeo(null, 'GEOMETRYCOLLECTION(POINT(0 0))');
+
+CREATE function print_isolated_nodes(lbl text)
+ RETURNS table(lbl text, msg text)
+AS $$
+DECLARE
+ sql text;
+BEGIN
+ sql := 'SELECT ' || quote_literal(lbl) || '::text, count(node_id)
+ || '' isolated nodes in face '' || containing_face
+ FROM t.node WHERE containing_face IS NOT NULL GROUP by containing_face
+ ORDER BY count(node_id), containing_face';
+ RETURN QUERY EXECUTE sql;
+END;
+$$ LANGUAGE 'plpgsql';
+
+CREATE function print_elements_count(lbl text)
+ RETURNS table(lbl text, nodes text, edges text, faces text)
+AS $$
+DECLARE
+ sql text;
+BEGIN
+ sql := 'select ' || quote_literal(lbl) || '::text,
+ ( select count(node_id) || '' nodes'' from t.node ) as nodes,
+ ( select count(edge_id) || '' edges'' from t.edge ) as edges,
+ ( select count(face_id) || '' faces'' from t.face
+ where face_id <> 0 ) as faces';
+ RETURN QUERY EXECUTE sql;
+END;
+$$ LANGUAGE 'plpgsql';
+
+
+-- Invalid geometries
+select null from ( select topology.CreateTopology('t', 4326) > 0 ) as ct;
+select topology.st_createtopogeo('t', null); -- Invalid geometry
+select 'invalid_srid', topology.st_createtopogeo('t', 'POINT(0 0)');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Single point
+select null from ( select topology.CreateTopology('t') > 0 ) as ct;
+select 'T1', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'POINT(0 0)'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T1');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Single line
+select null from ( select topology.CreateTopology('t', 4326) > 0 ) as ct;
+select 'T2', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'SRID=4326;LINESTRING(0 0, 8 -40)'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T2');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Single polygon with no holes
+select null from ( select topology.CreateTopology('t', 4326) > 0 ) as ct;
+select 'T3', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'SRID=4326;POLYGON((0 0, 8 -40, 70 34, 0 0))'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T3');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Single polygon with an hole
+select null from ( select topology.CreateTopology('t', 4326) > 0 ) as ct;
+select 'T4', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'SRID=4326;POLYGON((0 0, 10 0, 10 10, 0 10, 0 0),(5 5, 8 9, 4 2, 5 5))'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T4');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Multi point with duplicated points
+select null from ( select topology.CreateTopology('t') > 0 ) as ct;
+select 'T5', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'MULTIPOINT(0 0, 5 5, 0 0, 10 -2, 5 5, 0 0)'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T5');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Multi line with duplicated lines
+select null from ( select topology.CreateTopology('t') > 0 ) as ct;
+select 'T6', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'MULTILINESTRING((0 0, 10 0),(10 0, 0 0))'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T6');
+select * from print_isolated_nodes('T6');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Multi line with crossing lines
+select null from ( select topology.CreateTopology('t') > 0 ) as ct;
+select 'T7', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'MULTILINESTRING((0 0, 10 0),(5 -5, 6 5))'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T7');
+select * from print_isolated_nodes('T7');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Multi polygon with duplicated polygons
+select null from ( select topology.CreateTopology('t') > 0 ) as ct;
+select 'T8', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'MULTIPOLYGON(
+ ((0 0,10 0,10 10,0 10,0 0)),
+ ((0 0,0 10,10 10,10 0,0 0))
+)'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T8');
+select * from print_isolated_nodes('T8');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Multi polygon with overlapping polygons
+select null from ( select topology.CreateTopology('t') > 0 ) as ct;
+select 'T9', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'MULTIPOLYGON(
+ ((0 0,10 0,10 10,0 10,0 0)),
+ ((5 5,5 15,15 15,15 5,5 5))
+)'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T9');
+select * from print_isolated_nodes('T9');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Multi polygon with touching polygons
+select null from ( select topology.CreateTopology('t') > 0 ) as ct;
+select 'T10', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'MULTIPOLYGON(
+ ((0 0,5 10,10 0,0 0)),
+ ((0 20,5 10,10 20,0 20))
+)'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T10');
+select * from print_isolated_nodes('T10');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Collection of line and point within it
+select null from ( select topology.CreateTopology('t') > 0 ) as ct;
+select 'T11', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'GEOMETRYCOLLECTION(LINESTRING(0 0, 10 0),POINT(5 0))'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T11');
+select * from print_isolated_nodes('T11');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Collection of line and points on line's endpoint
+select null from ( select topology.CreateTopology('t') > 0 ) as ct;
+select 'T12', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'GEOMETRYCOLLECTION(LINESTRING(0 0, 10 0),POINT(0 0),POINT(10 0))'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T12');
+select * from print_isolated_nodes('T12');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Collection of line, points and polygons with various crossing and
+-- overlaps
+select null from ( select topology.CreateTopology('t') > 0 ) as ct;
+select 'T13', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'GEOMETRYCOLLECTION(
+ MULTIPOLYGON(
+ ((0 0,10 0,10 10,0 10,0 0)),
+ ((5 5,5 15,15 15,15 5,5 5), (10 10, 12 10, 10 12, 10 10))
+ ),
+ LINESTRING(0 0, 20 0),
+ MULTIPOINT(0 0,10 0,5 0),
+ MULTILINESTRING((0 0, 10 0),(10 0, 15 5)),
+ POINT(5 0),
+ POINT(10.5 10.5),
+ POINT(100 500)
+)'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T13');
+select * from print_isolated_nodes('T13');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+-- Collection of all geometries which can be derivated by the
+-- well-known city_data topology
+select null from ( select topology.CreateTopology('t') > 0 ) as ct;
+select 'T14', st_asewkt(g) FROM (
+SELECT g, topology.st_createtopogeo('t', g) FROM ( SELECT
+'GEOMETRYCOLLECTION(LINESTRING(8 30,16 30,16 38,3 38,3 30,8 30),POINT(4 31),LINESTRING(4 31,7 31,7 34,4 34,4 31),POINT(8 30),POINT(9 6),LINESTRING(9 6,9 14),LINESTRING(9 6,21 6),POLYGON((9 14,21 14,21 6,9 6,9 14)),POINT(9 14),LINESTRING(9 14,9 22),LINESTRING(9 14,21 14),POLYGON((9 22,21 22,21 14,9 14,9 22)),POINT(9 22),LINESTRING(9 22,21 22),POINT(9 35),LINESTRING(9 35,13 35),POINT(13 35),POLYGON((25 30,17 30,17 40,31 40,31 30,25 30)),POINT(20 37),POINT(21 6),LINESTRING(21 6,21 14),LINESTRING(21 6,35 6),POLYGON((21 14,35 14,35 6,21 6,21 14)),POINT(21 14),LINESTRING(21 14,21 22),LINESTRING(35 14,21 14),POLYGON((21 22,35 22,35 14,21 14,21 22)),POINT(21 22),LINESTRING(21 22,35 22),POINT(25 30),LINESTRING(25 30,25 35),POINT(25 35),POINT(35 6),LINESTRING(35 6,35 14),LINESTRING(35 6,47 6),POLYGON((35 14,47 14,47 6,35 6,35 14)),POINT(35 14),LINESTRING(35 14,35 22),LINESTRING(35 14,47 14),POLYGON((35 22,47 22,47 14,35 14,35 22)),POINT(35 22),LINESTRING(35 22,47 22),LINESTRING(36 38,38 35,41 34,42 33,45 32,47 28,50 28,52 32,57 33),POINT(36 38),LINESTRING(41 40,45 40,47 42,62 41,61 38,59 39,57 36,57 33),POINT(41 40),POINT(47 6),LINESTRING(47 6,47 14),POINT(47 14),LINESTRING(47 14,47 22),POINT(47 22),POINT(57 33))'
+::geometry as g ) as i ) as j;
+select * from print_elements_count('T14');
+select * from print_isolated_nodes('T14');
+select null from ( select topology.DropTopology('t') ) as dt;
+
+
+-- clean up
+DELETE FROM spatial_ref_sys where srid = 4326;
+DROP FUNCTION print_isolated_nodes(text);
+DROP FUNCTION print_elements_count(text);
--- /dev/null
+ERROR: SQL/MM Spatial exception - invalid topology name
+ERROR: SQL/MM Spatial exception - invalid topology name
+ERROR: SQL/MM Spatial exception - null argument
+ERROR: SQL/MM Spatial exception - null argument
+ERROR: Geometry SRID (-1) does not match topology SRID (4326)
+T1|POINT(0 0)
+T1|1 nodes|0 edges|0 faces
+T2|SRID=4326;LINESTRING(0 0,8 -40)
+T2|2 nodes|1 edges|0 faces
+T3|SRID=4326;POLYGON((0 0,8 -40,70 34,0 0))
+T3|1 nodes|1 edges|1 faces
+T4|SRID=4326;POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,8 9,4 2,5 5))
+T4|2 nodes|2 edges|2 faces
+T5|MULTIPOINT(0 0,5 5,0 0,10 -2,5 5,0 0)
+T5|3 nodes|0 edges|0 faces
+T6|MULTILINESTRING((0 0,10 0),(10 0,0 0))
+T6|2 nodes|1 edges|0 faces
+T7|MULTILINESTRING((0 0,10 0),(5 -5,6 5))
+T7|5 nodes|4 edges|0 faces
+T8|MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)),((0 0,0 10,10 10,10 0,0 0)))
+T8|1 nodes|1 edges|1 faces
+T9|MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)),((5 5,5 15,15 15,15 5,5 5)))
+T9|2 nodes|4 edges|3 faces
+T10|MULTIPOLYGON(((0 0,5 10,10 0,0 0)),((0 20,5 10,10 20,0 20)))
+T10|1 nodes|2 edges|2 faces
+T11|GEOMETRYCOLLECTION(LINESTRING(0 0,10 0),POINT(5 0))
+T11|3 nodes|2 edges|0 faces
+T12|GEOMETRYCOLLECTION(LINESTRING(0 0,10 0),POINT(0 0),POINT(10 0))
+T12|2 nodes|1 edges|0 faces
+T13|GEOMETRYCOLLECTION(MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)),((5 5,5 15,15 15,15 5,5 5),(10 10,12 10,10 12,10 10))),LINESTRING(0 0,20 0),MULTIPOINT(0 0,10 0,5 0),MULTILINESTRING((0 0,10 0),(10 0,15 5)),POINT(5 0),POINT(10.5 10.5),POINT(100 500))
+T13|10 nodes|12 edges|5 faces
+T13|1 isolated nodes in face 0
+T13|1 isolated nodes in face 1
+T14|GEOMETRYCOLLECTION(LINESTRING(8 30,16 30,16 38,3 38,3 30,8 30),POINT(4 31),LINESTRING(4 31,7 31,7 34,4 34,4 31),POINT(8 30),POINT(9 6),LINESTRING(9 6,9 14),LINESTRING(9 6,21 6),POLYGON((9 14,21 14,21 6,9 6,9 14)),POINT(9 14),LINESTRING(9 14,9 22),LINESTRING(9 14,21 14),POLYGON((9 22,21 22,21 14,9 14,9 22)),POINT(9 22),LINESTRING(9 22,21 22),POINT(9 35),LINESTRING(9 35,13 35),POINT(13 35),POLYGON((25 30,17 30,17 40,31 40,31 30,25 30)),POINT(20 37),POINT(21 6),LINESTRING(21 6,21 14),LINESTRING(21 6,35 6),POLYGON((21 14,35 14,35 6,21 6,21 14)),POINT(21 14),LINESTRING(21 14,21 22),LINESTRING(35 14,21 14),POLYGON((21 22,35 22,35 14,21 14,21 22)),POINT(21 22),LINESTRING(21 22,35 22),POINT(25 30),LINESTRING(25 30,25 35),POINT(25 35),POINT(35 6),LINESTRING(35 6,35 14),LINESTRING(35 6,47 6),POLYGON((35 14,47 14,47 6,35 6,35 14)),POINT(35 14),LINESTRING(35 14,35 22),LINESTRING(35 14,47 14),POLYGON((35 22,47 22,47 14,35 14,35 22)),POINT(35 22),LINESTRING(35 22,47 22),LINESTRING(36 38,38 35,41 34,42 33,45 32,47 28,50 28,52 32,57 33),POINT(36 38),LINESTRING(41 40,45 40,47 42,62 41,61 38,59 39,57 36,57 33),POINT(41 40),POINT(47 6),LINESTRING(47 6,47 14),POINT(47 14),LINESTRING(47 14,47 22),POINT(47 22),POINT(57 33))
+T14|22 nodes|24 edges|9 faces
+T14|1 isolated nodes in face 3