From 354cf4208852360b01e6603c47ccb0d8ae90e871 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Thu, 6 Dec 2012 23:23:57 +0000 Subject: [PATCH] Provide a version of toTopoGeom taking a TopoGeometry object Such version would _add_ the space taken by the input geometry to an existing TopoGeometry. git-svn-id: http://svn.osgeo.org/postgis/trunk@10809 b70326c6-7e19-0410-871a-916f4a2858ee --- NEWS | 3 +- topology/sql/topogeometry/totopogeom.sql.in.c | 141 ++++++++++++++++-- topology/test/regress/totopogeom.sql | 20 ++- topology/test/regress/totopogeom_expected | 3 + 4 files changed, 154 insertions(+), 13 deletions(-) diff --git a/NEWS b/NEWS index bd203fab4..6652b19c6 100644 --- a/NEWS +++ b/NEWS @@ -29,7 +29,8 @@ PostGIS 2.1.0 * New Features * - - topology.clearTopoGeom (Sandro Santilli / Vizzuality) + - toTopoGeom with TopoGeometry sink (Sandro Santilli / Vizzuality) + - clearTopoGeom (Sandro Santilli / Vizzuality) - ST_Segmentize(geography) (Paul Ramsey / OpenGeo) - ST_DelaunayTriangles (Sandro Santilli / Vizzuality) - ST_NearestValue, ST_Neighborhood (Bborie Park / UC Davis) diff --git a/topology/sql/topogeometry/totopogeom.sql.in.c b/topology/sql/topogeometry/totopogeom.sql.in.c index 20bdf939d..74525a8d2 100644 --- a/topology/sql/topogeometry/totopogeom.sql.in.c +++ b/topology/sql/topogeometry/totopogeom.sql.in.c @@ -28,7 +28,6 @@ DECLARE elems topology.TopoElementArray = '{{0,0}}'; sql TEXT; typ TEXT; - tolerance FLOAT8; BEGIN -- Get topology information @@ -42,9 +41,6 @@ BEGIN atopology; END; - -- Get tolerance, if 0 was given - tolerance := COALESCE( NULLIF(atolerance, 0), topology._st_mintolerance(atopology, ageom) ); - -- Get layer information BEGIN SELECT *, CASE @@ -114,7 +110,124 @@ BEGIN 'Unsupported feature type %', typ; END IF; - -- Now that we have a topogeometry, we loop over distinct components + tg := topology.toTopoGeom(ageom, tg, atolerance); + + RETURN tg; + +END +$$ +LANGUAGE 'plpgsql' VOLATILE STRICT; +-- } + +-- { +-- Convert a simple geometry to a topologically-defined one +-- adding its components to a pre-existing TopoGeometry +-- +-- }{ +CREATE OR REPLACE FUNCTION topology.toTopoGeom(ageom Geometry, tg topology.TopoGeometry, atolerance float8 DEFAULT 0) + RETURNS topology.TopoGeometry +AS +$$ +DECLARE + layer_info RECORD; + topology_info RECORD; + rec RECORD; + elems topology.TopoElementArray = '{{0,0}}'; + sql TEXT; + typ TEXT; + tolerance FLOAT8; + alayer INT; + atopology TEXT; +BEGIN + + RAISE NOTICE 'TopoGeometry is "%", its topology_id is "%"', tg, topology_id(tg); + + -- Get topology information + SELECT id, name FROM topology.topology + INTO topology_info + WHERE id = topology_id(tg); + IF NOT FOUND THEN + RAISE EXCEPTION 'No topology with id "%" in topology.topology', + topology_id(tg); + END IF; + + alayer := layer_id(tg); + atopology := topology_info.name; + + + -- Get tolerance, if 0 was given + tolerance := COALESCE( NULLIF(atolerance, 0), topology._st_mintolerance(topology_info.name, ageom) ); + + -- Get layer information + BEGIN + SELECT *, CASE + WHEN feature_type = 1 THEN 'puntal' + WHEN feature_type = 2 THEN 'lineal' + WHEN feature_type = 3 THEN 'areal' + WHEN feature_type = 4 THEN 'mixed' + ELSE 'unexpected_'||feature_type + END as typename + FROM topology.layer l + INTO STRICT layer_info + WHERE l.layer_id = layer_id(tg) + AND l.topology_id = topology_info.id; + EXCEPTION + WHEN NO_DATA_FOUND THEN + RAISE EXCEPTION 'No layer with id "%" in topology "%"', + alayer, atopology; + END; + + -- Can't convert to a hierarchical topogeometry + IF layer_info.level > 0 THEN + RAISE EXCEPTION 'Layer "%" of topology "%" is hierarchical, cannot convert a simple geometry to it.', + alayer, atopology; + END IF; + + + -- + -- Check type compatibility and set TopoGeometry type + -- 1:puntal, 2:lineal, 3:areal, 4:collection + -- + typ = geometrytype(ageom); + IF typ = 'GEOMETRYCOLLECTION' THEN + -- A collection can only go to collection layer + IF layer_info.feature_type != 4 THEN + RAISE EXCEPTION + 'Layer "%" of topology "%" is %, cannot hold a collection feature.', + layer_info.layer_id, topology_info.name, layer_info.typename; + END IF; + tg.type := 4; + ELSIF typ = 'POINT' OR typ = 'MULTIPOINT' THEN -- puntal + -- A point can go in puntal or collection layer + IF layer_info.feature_type != 4 and layer_info.feature_type != 1 THEN + RAISE EXCEPTION + 'Layer "%" of topology "%" is %, cannot hold a puntal feature.', + layer_info.layer_id, topology_info.name, layer_info.typename; + END IF; + tg.type := 1; + ELSIF typ = 'LINESTRING' or typ = 'MULTILINESTRING' THEN -- lineal + -- A line can go in lineal or collection layer + IF layer_info.feature_type != 4 and layer_info.feature_type != 2 THEN + RAISE EXCEPTION + 'Layer "%" of topology "%" is %, cannot hold a lineal feature.', + layer_info.layer_id, topology_info.name, layer_info.typename; + END IF; + tg.type := 2; + ELSIF typ = 'POLYGON' OR typ = 'MULTIPOLYGON' THEN -- areal + -- An area can go in areal or collection layer + IF layer_info.feature_type != 4 and layer_info.feature_type != 3 THEN + RAISE EXCEPTION + 'Layer "%" of topology "%" is %, cannot hold an areal feature.', + layer_info.layer_id, topology_info.name, layer_info.typename; + END IF; + tg.type := 3; + ELSE + -- Should never happen + RAISE EXCEPTION + 'Unexpected feature dimension %', ST_Dimension(ageom); + END IF; + + -- Now that we have an empty topogeometry, we loop over distinct components -- and add them to the definition of it. We add them as soon -- as possible so that each element can further edit the -- definition by splitting @@ -124,20 +237,26 @@ BEGIN WHEN ST_Dimension(geom) = 2 THEN 3 END as type, CASE WHEN ST_Dimension(geom) = 0 THEN - topology.topogeo_addPoint(atopology, geom, tolerance) + topology.topogeo_addPoint(topology_info.name, geom, tolerance) WHEN ST_Dimension(geom) = 1 THEN - topology.topogeo_addLineString(atopology, geom, tolerance) + topology.topogeo_addLineString(topology_info.name, geom, tolerance) WHEN ST_Dimension(geom) = 2 THEN - topology.topogeo_addPolygon(atopology, geom, tolerance) + topology.topogeo_addPolygon(topology_info.name, geom, tolerance) END as primitive FROM (SELECT (ST_Dump(ageom)).geom) as f WHERE NOT ST_IsEmpty(geom) LOOP -- TODO: consider use a single INSERT statement for the whole thing - sql := 'INSERT INTO ' || quote_ident(atopology) - || '.relation(topogeo_id, layer_id, element_type, element_id) VALUES (' + sql := 'INSERT INTO ' || quote_ident(topology_info.name) + || '.relation(topogeo_id, layer_id, element_type, element_id) SELECT ' || rec.id || ',' || rec.lyr || ',' || rec.type - || ',' || rec.primitive || ')'; + || ',' || rec.primitive + -- NOTE: we're avoiding duplicated rows here + || ' EXCEPT SELECT ' || rec.id || ', ' || rec.lyr + || ', element_type, element_id FROM ' + || quote_ident(topology_info.name) + || '.relation WHERE layer_id = ' || rec.lyr + || ' AND topogeo_id = ' || rec.id; #ifdef POSTGIS_TOPOLOGY_DEBUG RAISE DEBUG '%', sql; #endif diff --git a/topology/test/regress/totopogeom.sql b/topology/test/regress/totopogeom.sql index 54c92e825..afe230204 100644 --- a/topology/test/regress/totopogeom.sql +++ b/topology/test/regress/totopogeom.sql @@ -9,7 +9,7 @@ select totopogeom('POINT(0 0)'::geometry, 'tt', 1); select totopogeom(null, 'tt', 1); select totopogeom('POINT(0 0)'::geometry, '', 1); select totopogeom('POINT(0 0)'::geometry, null, 1); -select totopogeom('POINT(0 0)'::geometry, 'tt', null); +select totopogeom('POINT(0 0)'::geometry, 'tt', null::integer); -- Create simple puntual layer (will be layer 1) CREATE TABLE tt.f_puntal(id serial); @@ -169,6 +169,24 @@ with inp as ( select tg as ( select totopogeom(g, 'tt', 5) as g from inp ) select '#1790.3', ST_HausdorffDistance(inp.g, tg.g::geometry), ST_HausdorffDistance(tg.g::geometry, inp.g) FROM inp, tg; +-- Test adding portions to an existing TopoGeometry +INSERT INTO tt.f_areal (id, g) + SELECT -1, toTopoGeom('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))', 'tt', 4); +SELECT 'tgup1.1', id(t.g), st_area(t.g), count(r.*) + FROM tt.f_areal t, tt.relation r + WHERE t.id = -1 AND r.layer_id = 4 AND r.topogeo_id = id(t.g) + GROUP BY id(t.g), st_area(t.g); +UPDATE tt.f_areal SET g = toTopoGeom(st_translate(g, st_xmax(g::geometry)+1, 0), g); +SELECT 'tgup1.2', id(t.g), st_area(t.g), count(r.*) + FROM tt.f_areal t, tt.relation r + WHERE t.id = -1 AND r.layer_id = 4 AND r.topogeo_id = id(t.g) + GROUP BY id(t.g), st_area(t.g); +-- now add a smaller area +UPDATE tt.f_areal SET g = toTopoGeom(st_buffer(g, -1), g); +SELECT 'tgup1.3', id(t.g), st_area(t.g), count(r.*) + FROM tt.f_areal t, tt.relation r + WHERE t.id = -1 AND r.layer_id = 4 AND r.topogeo_id = id(t.g) + GROUP BY id(t.g), st_area(t.g); DROP TABLE tt.f_coll; DROP TABLE tt.f_areal; diff --git a/topology/test/regress/totopogeom_expected b/topology/test/regress/totopogeom_expected index 1ce95d7fb..ae7ddac6d 100644 --- a/topology/test/regress/totopogeom_expected +++ b/topology/test/regress/totopogeom_expected @@ -39,4 +39,7 @@ custom_search_path|0 #1790.1|0|0 #1790.2|0|0 #1790.3|0|0 +tgup1.1|5|100|1 +tgup1.2|5|200|2 +tgup1.3|5|200|4 Topology 'tt' dropped -- 2.50.1