From 42f41926b78d7b9f76e03e2b94cd7dde73b4ed8c Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Wed, 22 Dec 2010 17:27:02 +0000 Subject: [PATCH] Implement topology.AddFace and add test git-svn-id: http://svn.osgeo.org/postgis/trunk@6471 b70326c6-7e19-0410-871a-916f4a2858ee --- topology/sql/populate.sql | 187 +++++++++++++++++++++++++ topology/test/Makefile | 2 +- topology/test/regress/addface.sql | 32 +++++ topology/test/regress/addface_expected | 19 +++ 4 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 topology/test/regress/addface.sql create mode 100644 topology/test/regress/addface_expected diff --git a/topology/sql/populate.sql b/topology/sql/populate.sql index 284712143..89e1a3211 100644 --- a/topology/sql/populate.sql +++ b/topology/sql/populate.sql @@ -259,3 +259,190 @@ END $$ LANGUAGE 'plpgsql'; --} AddEdge + +--{ +-- +-- AddFace(atopology, poly) +-- +-- Add a face primitive to a topology and get it's identifier. +-- Returns an existing face at the same location, if any. +-- +-- For a newly added face, its edges will be appropriately +-- linked (marked as left-face or right-face). +-- +-- The target topology is assumed to be valid (containing no +-- self-intersecting edges). +-- +-- An exception is raised if: +-- o The polygon boundary is not fully defined by existing edges. +-- o The polygon overlaps an existing face. +-- +-- +CREATE OR REPLACE FUNCTION topology.AddFace(varchar, geometry) + RETURNS int +AS +$$ +DECLARE + atopology ALIAS FOR $1; + apoly ALIAS FOR $2; + bounds geometry; + symdif geometry; + faceid int; + rec RECORD; + relate text; + right_edges int[]; + left_edges int[]; + all_edges geometry; + old_faceid int; + old_edgeid int; +BEGIN + -- + -- Atopology and apoly are required + -- + IF atopology IS NULL OR apoly IS NULL THEN + RAISE EXCEPTION 'Invalid null argument'; + END IF; + + -- + -- Aline must be a polygon + -- + IF substring(geometrytype(apoly), 1, 4) != 'POLY' + THEN + RAISE EXCEPTION 'Face geometry must be a polygon'; + END IF; + + -- + -- Find all bounds edges, forcing right-hand-rule + -- to know what's left and what's right... + -- + bounds = ST_Boundary(ST_ForceRHR(apoly)); + + FOR rec IN EXECUTE 'SELECT geom, edge_id, ' + || 'left_face, right_face, ' + || '(st_dump(st_sharedpaths(geom, ' + || quote_literal(bounds::text) + || '))).path[1] as side FROM ' + || quote_ident(atopology) || '.edge ' + || 'WHERE ' + || quote_literal(bounds::text) + || '::geometry && geom AND ST_Relate(geom, ' + || quote_literal(bounds::text) + || ', ''1FF******'')' + LOOP + RAISE DEBUG 'Edge % (left:%, right:%) - side : %', + rec.edge_id, rec.left_face, rec.right_face, rec.side; + + IF rec.side = 1 THEN + -- This face is on the right + right_edges := array_append(right_edges, rec.edge_id); + old_faceid = rec.right_face; + ELSE + -- This face is on the left + left_edges := array_append(left_edges, rec.edge_id); + old_faceid = rec.left_face; + END IF; + + IF faceid IS NULL OR faceid = 0 THEN + faceid = old_faceid; + old_edgeid = rec.edge_id; + ELSIF faceid != old_faceid THEN + RAISE EXCEPTION 'Edge % has face % registered on the side of this face, while edge % has face % on the same side', rec.edge_id, old_faceid, old_edgeid, faceid; + END IF; + + -- Collect all edges for final full coverage check + all_edges = ST_Collect(all_edges, rec.geom); + + END LOOP; + + IF all_edges IS NULL THEN + RAISE EXCEPTION 'Found no edges on the polygon boundary'; + END IF; + + RAISE DEBUG 'Left edges: %', left_edges; + RAISE DEBUG 'Right edges: %', right_edges; + + -- + -- Check that all edges found, taken togheter, + -- fully match the polygon boundary and nothing more + -- + -- If the test fail either we need to add more edges + -- from the polygon boundary or we need to split + -- some of the existing ones. + -- + IF NOT ST_isEmpty(ST_SymDifference(bounds, all_edges)) THEN + IF NOT ST_isEmpty(ST_Difference(bounds, all_edges)) THEN + RAISE EXCEPTION 'Polygon boundary is not fully defined by existing edges'; + ELSE + RAISE EXCEPTION 'Existing edges cover polygon boundary and more! (invalid topology?)'; + END IF; + END IF; + +-- EXECUTE 'SELECT ST_Collect(geom) FROM' +-- || quote_ident(atopology) +-- || '.edge_data ' +-- || ' WHERE edge_id = ANY(' +-- || quote_literal(array_append(left_edges, right_edges)) +-- || ') '; + + -- + -- TODO: + -- Check that NO edge is contained in the face ? + -- + RAISE WARNING 'Not checking if face contains any edge'; + + + IF faceid IS NOT NULL AND faceid != 0 THEN + RAISE DEBUG 'Face already known as %', faceid; + RETURN faceid; + END IF; + + -- + -- Get new face id from sequence + -- + FOR rec IN EXECUTE 'SELECT nextval(''' || + atopology || '.face_face_id_seq'')' + LOOP + faceid = rec.nextval; + END LOOP; + + -- + -- Insert new face + -- + EXECUTE 'INSERT INTO ' + || quote_ident(atopology) + || '.face(face_id, mbr) VALUES(' + -- face_id + || faceid || ',' + -- minimum bounding rectangle + || quote_literal(Box2d(apoly)) + || ')'; + + -- + -- Update all edges having this face on the left + -- + EXECUTE 'UPDATE ' + || quote_ident(atopology) + || '.edge_data SET left_face = ' + || quote_literal(faceid) + || ' WHERE edge_id = ANY(' + || quote_literal(left_edges) + || ') '; + + -- + -- Update all edges having this face on the right + -- + EXECUTE 'UPDATE ' + || quote_ident(atopology) + || '.edge_data SET right_face = ' + || quote_literal(faceid) + || ' WHERE edge_id = ANY(' + || quote_literal(right_edges) + || ') '; + + + RETURN faceid; + +END +$$ +LANGUAGE 'plpgsql'; +--} AddFace diff --git a/topology/test/Makefile b/topology/test/Makefile index 78b12dfba..bec716b2e 100644 --- a/topology/test/Makefile +++ b/topology/test/Makefile @@ -78,7 +78,7 @@ clean distclean: TESTS = regress/legacy_validate.sql regress/legacy_predicate.sql \ regress/legacy_invalid.sql regress/sqlmm.sql \ regress/legacy_query.sql regress/addnode.sql \ - regress/addedge.sql + regress/addedge.sql regress/addface.sql check: topo_predicates.sql $(MAKE) -C ../../regress postgis.sql staged-install diff --git a/topology/test/regress/addface.sql b/topology/test/regress/addface.sql new file mode 100644 index 000000000..ffc5173f7 --- /dev/null +++ b/topology/test/regress/addface.sql @@ -0,0 +1,32 @@ +set client_min_messages to WARNING; + +-- Test with zero tolerance + +SELECT topology.CreateTopology('tt') > 0; + +-- Register a face in absence of edges (exception expected) +SELECT 'f*', topology.addFace('tt', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))'); + +-- Create 4 edges + +SELECT 'e1', topology.addEdge('tt', 'LINESTRING(0 0, 10 0)'); +SELECT 'e2', topology.addEdge('tt', 'LINESTRING(10 0, 10 10)'); +SELECT 'e3', topology.addEdge('tt', 'LINESTRING(0 10, 10 10)'); +SELECT 'e4', topology.addEdge('tt', 'LINESTRING(0 0, 0 10)'); + +-- Add one edge only incident on a vertex +SELECT 'e5', topology.addEdge('tt', 'LINESTRING(0 0, 0 -10)'); + +-- Register a face with no holes +SELECT 'f1', topology.addFace('tt', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))'); + +-- Register the _same_ face again +SELECT 'f1*', topology.addFace('tt', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))'); + +-- Check added faces +SELECT face_id, mbr from tt.face ORDER by face_id; + +-- Check linking +SELECT edge_id, left_face, right_face from tt.edge ORDER by edge_id; + +SELECT topology.DropTopology('tt'); diff --git a/topology/test/regress/addface_expected b/topology/test/regress/addface_expected new file mode 100644 index 000000000..731e7c2bd --- /dev/null +++ b/topology/test/regress/addface_expected @@ -0,0 +1,19 @@ +t +ERROR: Found no edges on the polygon boundary +e1|1 +e2|2 +e3|3 +e4|4 +e5|5 +WARNING: Not checking if face contains any edge +f1|1 +WARNING: Not checking if face contains any edge +f1*|1 +0| +1|BOX(0 0,10 10) +1|1|0 +2|1|0 +3|0|1 +4|0|1 +5|0|0 +Topology 'tt' dropped