pggeodb.nancy.inra.fr/db_robot - db_robot on pggeodb.nancy.inra.fr
Previous topic Chapter index Next topic

Function: validatetopology

 

 

Schema

topology

 

Owner

albenard

 

Descriptions

args: topology_schema_name - Returns a set of validatetopology_returntype objects detailing issues with topology

 

Options

Option

Value

Returns

Set of topology.validatetopology_returntype

Language

plpgsql

Parameters

toponame varchar

 

Definition

CREATE OR REPLACE FUNCTION topology.validatetopology (
 toponame varchar
)
RETURNS SETOF topology.validatetopology_returntype AS
$span$
DECLARE

 retrec topology.ValidateTopology_ReturnType;
 rec RECORD;
 rec2 RECORD;
 i integer;
 invalid_edges integer[];
 invalid_faces integer[];
 sql text;
BEGIN

 -- Check for coincident nodes
 FOR rec IN EXECUTE 'SELECT a.node_id as id1, b.node_id as id2 FROM '
   || quote_ident(toponame) || '.node a, '
   || quote_ident(toponame) || '.node b '
      'WHERE a.node_id < b.node_id '
      ' AND ST_DWithin(a.geom, b.geom, 0)'
-- NOTE: see #1625 and #1789
 LOOP
   retrec.error = 'coincident nodes';
   retrec.id1 = rec.id1;
   retrec.id2 = rec.id2;
   RETURN NEXT retrec;
 END LOOP;

 -- Check for edge crossed nodes
 -- TODO: do this in the single edge loop

 FOR rec IN EXECUTE 'SELECT n.node_id as nid, e.edge_id as eid FROM '
   || quote_ident(toponame) || '.node n, '
   || quote_ident(toponame) || '.edge e '
      'WHERE e.start_node != n.node_id '
      'AND e.end_node != n.node_id '
      'AND ST_Within(n.geom, e.geom)'

 LOOP
   retrec.error = 'edge crosses node';
   retrec.id1 = rec.eid; -- edge_id
   retrec.id2 = rec.nid; -- node_id
   RETURN NEXT retrec;
 END LOOP;

 -- Scan all edges
 FOR rec IN EXECUTE 'SELECT e.geom, e.edge_id as id1, e.left_face, e.right_face FROM '
   || quote_ident(toponame) || '.edge e ORDER BY edge_id'
 LOOP

   -- Any invalid edge becomes a cancer for higher level complexes
   IF NOT ST_IsValid(rec.geom) THEN

     retrec.error = 'invalid edge';
     retrec.id1 = rec.id1;
     retrec.id2 = NULL;
     RETURN NEXT retrec;
     invalid_edges := array_append(invalid_edges, rec.id1);

     IF invalid_faces IS NULL OR NOT rec.left_face = ANY ( invalid_faces )
     THEN
       invalid_faces := array_append(invalid_faces, rec.left_face);
     END IF;

     IF rec.right_face != rec.left_face AND ( invalid_faces IS NULL OR
           NOT
rec.right_face = ANY ( invalid_faces ) )
     THEN
       invalid_faces := array_append(invalid_faces, rec.right_face);
     END IF;

     CONTINUE;

   END IF;

   IF NOT ST_IsSimple(rec.geom) THEN
     retrec.error = 'edge not simple';
     retrec.id1 = rec.id1;
     retrec.id2 = NULL;
     RETURN NEXT retrec;
   END IF;

 END LOOP;

 -- Check for edge crossing
 sql := 'SELECT e1.edge_id as id1, e2.edge_id as id2, '
      ' e1.geom as g1, e2.geom as g2, '
      'ST_Relate(e1.geom, e2.geom) as im FROM '

   || quote_ident(toponame) || '.edge e1, '
   || quote_ident(toponame) || '.edge e2 '
      'WHERE e1.edge_id < e2.edge_id '
      ' AND e1.geom && e2.geom '
;
 IF invalid_edges IS NOT NULL THEN
   sql := sql || ' AND NOT e1.edge_id = ANY ('
              || quote_literal(invalid_edges) || ')'
              || ' AND NOT e2.edge_id = ANY ('
              || quote_literal(invalid_edges) || ')';
 END IF;

 FOR rec IN EXECUTE sql
 LOOP

   IF ST_RelateMatch(rec.im, 'FF1F**1*2') THEN
     CONTINUE
; -- no interior intersection

   --
   -- Closed lines have no boundary, so endpoint
   -- intersection would be considered interior
   -- See http://trac.osgeo.org/postgis/ticket/770
   -- See also full explanation in topology.AddEdge
   --


   ELSIF ST_RelateMatch(rec.im, 'FF10F01F2') THEN
     -- first line (g1) is open, second (g2) is closed
     -- first boundary has puntual intersection with second interior
     --
     -- compute intersection, check it equals second endpoint

     IF ST_Equals(ST_Intersection(rec.g2, rec.g1),
                  ST_StartPoint(rec.g2))
     THEN
       CONTINUE
;
     END IF;

   ELSIF ST_RelateMatch(rec.im, 'F01FFF102') THEN
     -- second line (g2) is open, first (g1) is closed
     -- second boundary has puntual intersection with first interior
     --
     -- compute intersection, check it equals first endpoint

     IF ST_Equals(ST_Intersection(rec.g2, rec.g1),
                  ST_StartPoint(rec.g1))
     THEN
       CONTINUE
;
     END IF;

   ELSIF ST_RelateMatch(rec.im, '0F1FFF1F2') THEN
     -- both lines are closed (boundary intersects nothing)
     -- they have puntual intersection between interiors
     --
     -- compute intersection, check it's a single point
     -- and equals first StartPoint _and_ second StartPoint

     IF ST_Equals(ST_Intersection(rec.g1, rec.g2),
                  ST_StartPoint(rec.g1)) AND
        ST_Equals(ST_StartPoint(rec.g1), ST_StartPoint(rec.g2))
     THEN
       CONTINUE
;
     END IF;

   END IF;

   retrec.error = 'edge crosses edge';
   retrec.id1 = rec.id1;
   retrec.id2 = rec.id2;
   RETURN NEXT retrec;
 END LOOP;

 -- Check for edge start_node geometry mis-match
 -- TODO: move this in the first edge table scan

 FOR rec IN EXECUTE 'SELECT e.edge_id as id1, n.node_id as id2 FROM '
   || quote_ident(toponame) || '.edge e, '
   || quote_ident(toponame) || '.node n '
      'WHERE e.start_node = n.node_id '
      'AND NOT ST_Equals(ST_StartPoint(e.geom), n.geom)'

 LOOP
   retrec.error = 'edge start node geometry mis-match';
   retrec.id1 = rec.id1;
   retrec.id2 = rec.id2;
   RETURN NEXT retrec;
 END LOOP;

 -- Check for edge end_node geometry mis-match
 -- TODO: move this in the first edge table scan

 FOR rec IN EXECUTE 'SELECT e.edge_id as id1, n.node_id as id2 FROM '
   || quote_ident(toponame) || '.edge e, '
   || quote_ident(toponame) || '.node n '
      'WHERE e.end_node = n.node_id '
      'AND NOT ST_Equals(ST_EndPoint(e.geom), n.geom)'

 LOOP
   retrec.error = 'edge end node geometry mis-match';
   retrec.id1 = rec.id1;
   retrec.id2 = rec.id2;
   RETURN NEXT retrec;
 END LOOP;

 -- Check for faces w/out edges
 FOR rec IN EXECUTE 'SELECT face_id as id1 FROM '
   || quote_ident(toponame) || '.face '
   || 'WHERE face_id > 0 EXCEPT ( SELECT left_face FROM '
   || quote_ident(toponame) || '.edge '
   || ' UNION SELECT right_face FROM '
   || quote_ident(toponame) || '.edge '
   || ')'
 LOOP
   retrec.error = 'face without edges';
   retrec.id1 = rec.id1;
   retrec.id2 = NULL;
   RETURN NEXT retrec;
 END LOOP;

 -- Now create a temporary table to construct all face geometries
 -- for checking their consistency


 sql := 'CREATE TEMP TABLE face_check ON COMMIT DROP AS '
      'SELECT face_id, topology.ST_GetFaceGeometry('

   || quote_literal(toponame) || ', face_id) as geom, mbr FROM '
   || quote_ident(toponame) || '.face WHERE face_id > 0';
 IF invalid_faces IS NOT NULL THEN
   sql := sql || ' AND NOT face_id = ANY ('
              || quote_literal(invalid_faces) || ')';
 END IF;
 EXECUTE sql;

 -- Build a gist index on geom
 EXECUTE 'CREATE INDEX "face_check_gist" ON '
      'face_check USING gist (geom);'
;

 -- Build a btree index on id
 EXECUTE 'CREATE INDEX "face_check_bt" ON '
      'face_check (face_id);'
;

 -- Scan the table looking for NULL geometries
 FOR rec IN EXECUTE
   'SELECT f1.face_id FROM '
      'face_check f1 WHERE f1.geom IS NULL OR ST_IsEmpty(f1.geom)'

 LOOP
   -- Face missing !
   retrec.error := 'face has no rings';
   retrec.id1 := rec.face_id;
   retrec.id2 := NULL;
   RETURN NEXT retrec;
 END LOOP;


 -- Scan the table looking for overlap or containment
 -- TODO: also check for MBR consistency

 FOR rec IN EXECUTE
   'SELECT f1.geom, f1.face_id as id1, f2.face_id as id2, '
      ' ST_Relate(f1.geom, f2.geom) as im'
      ' FROM '
      'face_check f1, '
      'face_check f2 '
      'WHERE f1.face_id < f2.face_id'
      ' AND f1.geom && f2.geom'

 LOOP

   -- Face overlap
   IF ST_RelateMatch(rec.im, 'T*T***T**') THEN
   retrec.error = 'face overlaps face';
   retrec.id1 = rec.id1;
   retrec.id2 = rec.id2;
   RETURN NEXT retrec;
   END IF;

   -- Face 1 is within face 2
   IF ST_RelateMatch(rec.im, 'T*F**F***') THEN
   retrec.error = 'face within face';
   retrec.id1 = rec.id1;
   retrec.id2 = rec.id2;
   RETURN NEXT retrec;
   END IF;

   -- Face 1 contains face 2
   IF ST_RelateMatch(rec.im, 'T*****FF*') THEN
   retrec.error = 'face within face';
   retrec.id1 = rec.id2;
   retrec.id2 = rec.id1;
   RETURN NEXT retrec;
   END IF;

 END LOOP;


 DROP TABLE face_check;

 RETURN;
END
$span$
LANGUAGE
'plpgsql'
VOLATILE
RETURNS NULL ON NULL INPUT
SECURITY INVOKER
COST
100 ROWS 1000;

COMMENT ON FUNCTION topology.validatetopology(toponame varchar)
IS 'args: topology_schema_name - Returns a set of validatetopology_returntype objects detailing issues with topology';

This file was generated with SQL Manager for PostgreSQL (www.pgsqlmanager.com) at 26/02/2014 11:51
Previous topic Chapter index Next topic