postgis/topology/topology.sql.in
Regina Obe cf2b98cd00 Support topogeometry cast to topoelement
Use backing function TopoElement
Closes #5336 for PostGIS 3.4.0
2023-02-22 00:08:25 +01:00

1471 lines
38 KiB
MySQL

-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
--
-- PostGIS - Spatial Types for PostgreSQL
-- http://postgis.net
--
-- Copyright (C) 2010, 2011 Sandro Santilli <strk@kbt.io>
-- Copyright (C) 2005 Refractions Research Inc.
--
-- This is free software; you can redistribute and/or modify it under
-- the terms of the GNU General Public Licence. See the COPYING file.
--
-- Author: Sandro Santilli <strk@kbt.io>
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- STATUS:
--
-- All objects are created in the 'topology' schema.
--
-- We have PostGIS-specific objects and SQL/MM objects.
-- PostGIS-specific objects have no prefix, SQL/MM ones
-- have the ``ST_' prefix.
--
-- [PostGIS-specific]
--
-- TABLE topology
-- Table storing topology info (name, srid, precision)
--
-- TYPE TopoGeometry
-- Complex type storing topology_id, layer_id, geometry type
-- and topogeometry id.
--
-- DOMAIN TopoElement
-- An array of two elements: element_id and element_type.
-- In fact, an array of integers.
--
-- DOMAIN TopoElementArray
-- An array of element_id,element_type values.
-- In fact, a bidimensional array of integers:
-- '{{id,type}, {id,type}, ...}'
--
-- FUNCTION CreateTopology(name, [srid], [precision])
-- Initialize a new topology (creating schema with
-- edge,face,node,relation) and add a record into
-- the topology.topology table.
-- TODO: add triggers (or rules, or whatever) enforcing
-- precision to the edge and node tables.
--
-- FUNCTION DropTopology(name)
-- Delete a topology removing reference from the
-- topology.topology table
--
-- FUNCTION GetTopologyId(name)
-- FUNCTION GetTopologySRID(name)
-- FUNCTION GetTopologyName(id)
-- Return info about a Topology
--
-- FUNCTION AddTopoGeometryColumn(toponame, schema, table, column, geomtype)
-- Add a TopoGeometry column to a table, making it a topology layer.
-- Returns created layer id.
--
-- FUNCTION DropTopoGeometryColumn(schema, table, column)
-- Drop a TopoGeometry column, unregister the associated layer,
-- cleanup the relation table.
--
-- FUNCTION CreateTopoGeom(toponame, geomtype, layer_id, topo_objects)
-- Create a TopoGeometry object from existing Topology elements.
-- The "topo_objects" parameter is of TopoElementArray type.
--
-- FUNCTION GetTopoGeomElementArray(toponame, layer_id, topogeom_id)
-- FUNCTION GetTopoGeomElementArray(TopoGeometry)
-- Returns a TopoElementArray object containing the topological
-- elements of the given TopoGeometry.
--
-- FUNCTION GetTopoGeomElements(toponame, layer_id, topogeom_id)
-- FUNCTION GetTopoGeomElements(TopoGeometry)
-- Returns a set of TopoElement objects containing the
-- topological elements of the given TopoGeometry (primitive
-- elements)
--
-- FUNCTION ValidateTopology(toponame)
-- Run validity checks on the topology, returning, for each
-- detected error, a 3-columns row containing error string
-- and references to involved topo elements: error, id1, id2
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- Overloaded functions for TopoGeometry inputs
--
-- FUNCTION intersects(TopoGeometry, TopoGeometry)
-- FUNCTION equals(TopoGeometry, TopoGeometry)
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- FUNCTION TopoGeo_AddPoint(toponame, point)
-- Add a Point geometry to the topology
-- TODO: accept a topology/layer id
-- rework to use existing node if existent
--
-- FUNCTION TopoGeo_AddLinestring(toponame, line)
-- Add a LineString geometry to the topology
-- TODO: accept a topology/layer id
-- rework to use existing nodes/edges
-- splitting them if required
--
-- FUNCTION TopoGeo_AddPolygon(toponame, polygon)
-- Add a Polygon geometry to the topology
-- TODO: implement
--
-- TYPE GetFaceEdges_ReturnType
-- Complex type used to return tuples from ST_GetFaceEdges
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- [SQL/MM]
--
-- ST_InitTopoGeo
-- Done, can be modified to include explicit sequences or
-- more constraints. Very noisy due to implicit index creations
-- for primary keys and sequences for serial fields...
--
-- ST_CreateTopoGeo
-- Complete
--
-- ST_AddIsoNode
-- Complete
--
-- ST_RemoveIsoNode
-- Complete
--
-- ST_MoveIsoNode
-- Complete
--
-- ST_AddIsoEdge
-- Complete
--
-- ST_RemoveIsoEdge
-- Complete, exceptions untested
--
-- ST_ChangeEdgeGeom
-- Complete
--
-- ST_NewEdgesSplit
-- Complete
-- Also updates the Relation table
--
-- ST_ModEdgeSplit
-- Complete
-- Also updates the Relation table
--
-- ST_AddEdgeNewFaces
-- Complete
-- Also updates the Relation table
--
-- ST_AddEdgeModFace
-- Complete
-- Also updates the Relation table
--
-- ST_GetFaceEdges
-- Complete
--
-- ST_ModEdgeHeal
-- Complete
-- Also updates the Relation table
--
-- ST_NewEdgeHeal
-- Complete
-- Also updates the Relation table
--
-- ST_GetFaceGeometry
-- Implemented using ST_BuildArea()
--
-- ST_RemEdgeNewFace
-- Complete
-- Also updates the Relation table
--
-- ST_RemEdgeModFace
-- Complete
-- Also updates the Relation table
--
-- ST_ValidateTopoGeo
-- Unimplemented (probably a wrapper around ValidateTopology)
--
--
-- Uninstalling previous installation isn't really a good habit ...
-- Let people decide about that
-- DROP SCHEMA topology CASCADE;
#include "sqldefines.h"
-- INSTALL VERSION: POSTGIS_LIB_VERSION
CREATE SCHEMA topology;
COMMENT ON SCHEMA topology IS 'PostGIS Topology schema';
-- Doing everything outside of a transaction helps
-- upgrading in the best case.
BEGIN;
--={ ----------------------------------------------------------------
-- POSTGIS-SPECIFIC block
--
-- This part contains function NOT in the SQL/MM specification
--
---------------------------------------------------------------------
--
-- Topology table.
-- Stores id,name,precision and SRID of topologies.
--
CREATE TABLE topology.topology (
id SERIAL NOT NULL PRIMARY KEY,
name VARCHAR NOT NULL UNIQUE,
SRID INTEGER NOT NULL,
precision FLOAT8 NOT NULL,
hasz BOOLEAN NOT NULL DEFAULT false
);
--{ LayerTrigger()
--
-- Layer integrity trigger
--
CREATE OR REPLACE FUNCTION topology.LayerTrigger()
RETURNS trigger
AS
$$
DECLARE
rec RECORD;
ok BOOL;
toponame varchar;
query TEXT;
BEGIN
--RAISE NOTICE 'LayerTrigger called % % at % level', TG_WHEN, TG_OP, TG_LEVEL;
IF TG_OP = 'INSERT' THEN
RAISE EXCEPTION 'LayerTrigger not meant to be called on INSERT';
ELSIF TG_OP = 'UPDATE' THEN
RAISE EXCEPTION 'The topology.layer table cannot be updated';
END IF;
-- Check for existance of any feature column referencing
-- this layer
FOR rec IN SELECT * FROM pg_namespace n, pg_class c, pg_attribute a
WHERE text(n.nspname) = OLD.schema_name
AND c.relnamespace = n.oid
AND text(c.relname) = OLD.table_name
AND a.attrelid = c.oid
AND text(a.attname) = OLD.feature_column
LOOP
query = 'SELECT * '
' FROM ' || quote_ident(OLD.schema_name)
|| '.' || quote_ident(OLD.table_name)
|| ' WHERE layer_id('
|| quote_ident(OLD.feature_column)||') '
'=' || OLD.layer_id
|| ' LIMIT 1';
--RAISE NOTICE '%', query;
FOR rec IN EXECUTE query
LOOP
RAISE NOTICE 'A feature referencing layer % of topology % still exists in %.%.%', OLD.layer_id, OLD.topology_id, OLD.schema_name, OLD.table_name, OLD.feature_column;
RETURN NULL;
END LOOP;
END LOOP;
-- Get topology name
SELECT name FROM topology.topology INTO toponame
WHERE id = OLD.topology_id;
IF toponame IS NULL THEN
RAISE NOTICE 'Could not find name of topology with id %',
OLD.layer_id;
END IF;
-- Check if any record in the relation table references this layer
FOR rec IN SELECT c.oid FROM pg_namespace n, pg_class c
WHERE text(n.nspname) = toponame AND c.relnamespace = n.oid
AND c.relname = 'relation'
LOOP
query = 'SELECT * '
' FROM ' || quote_ident(toponame)
|| '.relation '
' WHERE layer_id = '|| OLD.layer_id
|| ' LIMIT 1';
--RAISE NOTICE '%', query;
FOR rec IN EXECUTE query
LOOP
RAISE NOTICE 'A record in %.relation still references layer %', toponame, OLD.layer_id;
RETURN NULL;
END LOOP;
END LOOP;
RETURN OLD;
END;
$$
LANGUAGE 'plpgsql' VOLATILE STRICT;
--} LayerTrigger()
--{
-- Layer table.
-- Stores topology layer informations
--
CREATE TABLE topology.layer (
topology_id INTEGER NOT NULL
REFERENCES topology.topology(id),
layer_id integer NOT NULL,
schema_name VARCHAR NOT NULL,
table_name VARCHAR NOT NULL,
feature_column VARCHAR NOT NULL,
feature_type integer NOT NULL,
level INTEGER NOT NULL DEFAULT 0,
child_id INTEGER DEFAULT NULL,
UNIQUE(schema_name, table_name, feature_column),
PRIMARY KEY(topology_id, layer_id)
);
CREATE TRIGGER layer_integrity_checks BEFORE UPDATE OR DELETE
ON topology.layer FOR EACH ROW EXECUTE PROCEDURE topology.LayerTrigger();
--} Layer table.
--
-- TopoGeometry type
--
-- Availability: 1.1.0
--
CREATE TYPE topology.TopoGeometry AS (
topology_id integer,
layer_id integer,
id integer,
type integer -- 1: [multi]point, 2: [multi]line,
-- 3: [multi]polygon, 4: collection
);
--
-- TopoElement domain
--
-- This is an array of two elements: element_id and element_type.
--
-- When used to define _simple_ TopoGeometries,
-- element_type can be:
-- 0: a node
-- 1: an edge
-- 2: a face
-- and element_id will be the node, edge or face identifier
--
-- When used to define _hierarchical_ TopoGeometries,
-- element_type will be the child layer identifier and
-- element_id will be composing TopoGeometry identifier
--
CREATE DOMAIN topology.TopoElement AS integer[]
CONSTRAINT DIMENSIONS CHECK (
array_upper(VALUE, 2) IS NULL
AND array_upper(VALUE, 1) = 2
);
ALTER DOMAIN topology.TopoElement ADD
CONSTRAINT lower_dimension CHECK (
array_lower(VALUE, 1) = 1
);
ALTER DOMAIN topology.TopoElement DROP CONSTRAINT
IF EXISTS
type_range;
ALTER DOMAIN topology.TopoElement ADD
CONSTRAINT type_range CHECK (
VALUE[2] > 0
);
--
-- TopoElementArray domain
--
-- Changed: 3.1.0 - get rid of IS NOT NULL check
CREATE DOMAIN topology.TopoElementArray AS integer[][]
CONSTRAINT DIMENSIONS CHECK (
array_upper(VALUE, 2) = 2
AND array_upper(VALUE, 3) IS NULL
);
-- Changed: 3.1.0
ALTER DOMAIN topology.TopoElementArray DROP CONSTRAINT
IF EXISTS
DIMENSIONS;
-- Changed: 3.1.0 - get rid of IS NOT NULL check
ALTER DOMAIN topology.TopoElementArray ADD
CONSTRAINT type_range CHECK (
array_upper(VALUE, 2) = 2
AND array_upper(VALUE, 3) IS NULL
);
--{ RelationTrigger()
--
-- Relation integrity trigger
--
CREATE OR REPLACE FUNCTION topology.RelationTrigger()
RETURNS trigger
AS
$$
DECLARE
toponame varchar;
topoid integer;
plyr RECORD; -- parent layer
rec RECORD;
ok BOOL;
BEGIN
IF TG_NARGS != 2 THEN
RAISE EXCEPTION 'RelationTrigger called with wrong number of arguments';
END IF;
topoid = TG_ARGV[0];
toponame = TG_ARGV[1];
--RAISE NOTICE 'RelationTrigger called % % on %.relation for a %', TG_WHEN, TG_OP, toponame, TG_LEVEL;
IF TG_OP = 'DELETE' THEN
RAISE EXCEPTION 'RelationTrigger not meant to be called on DELETE';
END IF;
-- Get layer info (and verify it exists)
ok = false;
FOR plyr IN EXECUTE 'SELECT * FROM topology.layer '
'WHERE '
' topology_id = ' || topoid
|| ' AND'
' layer_id = ' || NEW.layer_id
LOOP
ok = true;
EXIT;
END LOOP;
IF NOT ok THEN
RAISE EXCEPTION 'Layer % does not exist in topology %',
NEW.layer_id, topoid;
RETURN NULL;
END IF;
IF plyr.level > 0 THEN -- this is hierarchical layer
-- ElementType must be the layer child id
IF NEW.element_type != plyr.child_id THEN
RAISE EXCEPTION 'Type of elements in layer % must be set to its child layer id %', plyr.layer_id, plyr.child_id;
RETURN NULL;
END IF;
-- ElementId must be an existent TopoGeometry in child layer
ok = false;
FOR rec IN EXECUTE 'SELECT topogeo_id FROM '
|| quote_ident(toponame) || '.relation '
' WHERE layer_id = ' || plyr.child_id
|| ' AND topogeo_id = ' || NEW.element_id
LOOP
ok = true;
EXIT;
END LOOP;
IF NOT ok THEN
RAISE EXCEPTION 'TopoGeometry % does not exist in the child layer %', NEW.element_id, plyr.child_id;
RETURN NULL;
END IF;
ELSE -- this is a basic layer
-- ElementType must be compatible with layer type
IF plyr.feature_type != 4
AND plyr.feature_type != NEW.element_type
THEN
RAISE EXCEPTION 'Element of type % is not compatible with layer of type %', NEW.element_type, plyr.feature_type;
RETURN NULL;
END IF;
--
-- Now lets see if the element is consistent, which
-- is it exists in the topology tables.
--
--
-- Element is a Node
--
IF NEW.element_type = 1
THEN
ok = false;
FOR rec IN EXECUTE 'SELECT node_id FROM '
|| quote_ident(toponame) || '.node '
' WHERE node_id = ' || NEW.element_id
LOOP
ok = true;
EXIT;
END LOOP;
IF NOT ok THEN
RAISE EXCEPTION 'Node % does not exist in topology %', NEW.element_id, toponame;
RETURN NULL;
END IF;
--
-- Element is an Edge
--
ELSIF NEW.element_type = 2
THEN
ok = false;
FOR rec IN EXECUTE 'SELECT edge_id FROM '
|| quote_ident(toponame) || '.edge_data '
' WHERE edge_id = ' || abs(NEW.element_id)
LOOP
ok = true;
EXIT;
END LOOP;
IF NOT ok THEN
RAISE EXCEPTION 'Edge % does not exist in topology %', NEW.element_id, toponame;
RETURN NULL;
END IF;
--
-- Element is a Face
--
ELSIF NEW.element_type = 3
THEN
IF NEW.element_id = 0 THEN
RAISE EXCEPTION 'Face % cannot be associated with any feature', NEW.element_id;
RETURN NULL;
END IF;
ok = false;
FOR rec IN EXECUTE 'SELECT face_id FROM '
|| quote_ident(toponame) || '.face '
' WHERE face_id = ' || NEW.element_id
LOOP
ok = true;
EXIT;
END LOOP;
IF NOT ok THEN
RAISE EXCEPTION 'Face % does not exist in topology %', NEW.element_id, toponame;
RETURN NULL;
END IF;
END IF;
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql' VOLATILE STRICT;
--} RelationTrigger()
--{
-- DropTopoGeometryColumn(schema, table, colum)
--
-- Drop a TopoGeometry column, unregister the associated layer,
-- cleanup the relation table.
--
-- Availability: 1.1.0
--
CREATE OR REPLACE FUNCTION topology.DropTopoGeometryColumn(schema varchar, tbl varchar, col varchar)
RETURNS text
AS
$BODY$
DECLARE
rec RECORD;
lyrinfo RECORD;
ok BOOL;
result text;
sql TEXT;
BEGIN
-- Get layer and topology info
sql := $$
SELECT t.name as toponame, l.*
FROM topology.topology t, topology.layer l
WHERE l.topology_id = t.id
AND l.schema_name = $1
AND l.table_name = $2
AND l.feature_column = $3
$$;
ok := false;
FOR rec IN EXECUTE sql USING schema, tbl, col
LOOP
ok := true;
lyrinfo := rec;
END LOOP;
-- Layer not found
IF NOT ok THEN
RAISE EXCEPTION 'No layer registered on %.%.%',
schema,tbl,col;
END IF;
-- Cleanup the relation table (if it exists)
BEGIN
sql := format(
'DELETE FROM %I.relation WHERE layer_id = $1',
lyrinfo.toponame
);
EXECUTE sql USING lyrinfo.layer_id;
EXCEPTION
WHEN UNDEFINED_TABLE THEN
RAISE NOTICE '%', SQLERRM;
WHEN OTHERS THEN
RAISE EXCEPTION 'Got % (%)', SQLERRM, SQLSTATE;
END;
-- Drop the sequence for topogeoms in this layer
sql := format(
'DROP SEQUENCE IF EXISTS %I.topogeo_s_%s',
lyrinfo.toponame,
lyrinfo.layer_id
);
EXECUTE sql;
ok = false;
FOR rec IN SELECT * FROM pg_namespace n, pg_class c, pg_attribute a
WHERE text(n.nspname) = schema
AND c.relnamespace = n.oid
AND text(c.relname) = tbl
AND a.attrelid = c.oid
AND text(a.attname) = col
LOOP
ok = true;
EXIT;
END LOOP;
IF ok THEN
-- Drop the layer column
sql := format(
'ALTER TABLE %I.%I DROP %I CASCADE',
schema, tbl, col
);
EXECUTE sql;
END IF;
-- Delete the layer record
sql := $$
DELETE FROM topology.layer
WHERE topology_id = $1
AND layer_id = $2
$$;
EXECUTE sql USING lyrinfo.topology_id, lyrinfo.layer_id;
result := format(
'Layer %s (%I.%I.%I) dropped',
lyrinfo.layer_id, schema, tbl, col
);
RETURN result;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
--
--} DropTopoGeometryColumn
-- {
--
-- populate_topology_layer
--
-- Register missing layers into topology.topology, looking at
-- their constraints.
--
-- The function doesn't attempt to determine if a layer is
-- hierarchical or primitive, but always assumes primitive.
--
-- }{
DROP FUNCTION IF EXISTS topology.populate_topology_layer();
CREATE OR REPLACE FUNCTION topology.populate_topology_layer()
RETURNS TABLE(schema_name text, table_name text, feature_column text)
AS
$$
INSERT INTO topology.layer
WITH checks AS (
SELECT
n.nspname sch, r.relname tab,
replace(c.conname, 'check_topogeom_', '') col,
--c.consrc src,
regexp_matches(c.consrc,
'\.topology_id = (\d+).*\.layer_id = (\d+).*\.type = (\d+)') inf
FROM (SELECT conname, connamespace, conrelid, conkey, pg_get_constraintdef(oid) As consrc
FROM pg_constraint) AS c, pg_class r, pg_namespace n
WHERE c.conname LIKE 'check_topogeom_%'
AND r.oid = c.conrelid
AND n.oid = r.relnamespace
), newrows AS (
SELECT inf[1]::int as topology_id,
inf[2]::int as layer_id,
sch, tab, col, inf[3]::int as feature_type --, src
FROM checks c
WHERE NOT EXISTS (
SELECT * FROM topology.layer l
WHERE l.schema_name = c.sch
AND l.table_name = c.tab
AND l.feature_column = c.col
)
)
SELECT topology_id, layer_id, sch,
tab, col, feature_type,
0, NULL
FROM newrows RETURNING schema_name,table_name,feature_column;
$$
LANGUAGE 'sql' VOLATILE;
--{
-- CreateTopoGeom(topology_name, topogeom_type, layer_id, elements)
--
-- Create a TopoGeometry object from Topology elements.
-- The elements parameter is a two-dimensional array.
-- Every element of the array is either a Topology element represented by
-- (id, type) or a TopoGeometry element represented by (id, layer).
-- The actual semantic depends on the TopoGeometry layer, either at
-- level 0 (elements are topological primitives) or higer (elements
-- are TopoGeoms from child layer).
--
-- @param toponame Topology name
--
-- @param tg_type Spatial type of geometry
-- 1:[multi]point (puntal)
-- 2:[multi]line (lineal)
-- 3:[multi]poly (areal)
-- 4:collection (mixed)
--
-- @param layer_id Layer identifier
--
-- @param tg_objs Array of components
--
-- Return a topology.TopoGeometry object.
--
-- Availability: 1.1.0
--
CREATE OR REPLACE FUNCTION topology.CreateTopoGeom(toponame varchar, tg_type integer, layer_id integer, tg_objs topology.TopoElementArray)
RETURNS topology.TopoGeometry
AS
$$
DECLARE
i integer;
dims varchar;
outerdims varchar;
innerdims varchar;
obj_type integer;
obj_id integer;
ret topology.TopoGeometry;
rec RECORD;
layertype integer;
layerlevel integer;
layerchild integer;
BEGIN
IF tg_type < 1 OR tg_type > 4 THEN
RAISE EXCEPTION 'Invalid TopoGeometry type % (must be in the range 1..4)', tg_type;
END IF;
-- Get topology id into return TopoGeometry
SELECT id INTO ret.topology_id
FROM topology.topology WHERE name = toponame;
IF NOT FOUND THEN
RAISE EXCEPTION 'Topology % does not exist', quote_literal(toponame);
END IF;
--
-- Get layer info
--
layertype := NULL;
FOR rec IN EXECUTE 'SELECT * FROM topology.layer'
' WHERE topology_id = ' || ret.topology_id
|| ' AND layer_id = ' || layer_id
LOOP
layertype = rec.feature_type;
layerlevel = rec.level;
layerchild = rec.child_id;
END LOOP;
-- Check for existence of given layer id
IF layertype IS NULL THEN
RAISE EXCEPTION 'No layer with id % is registered with topology %', layer_id, toponame;
END IF;
-- Verify compatibility between layer geometry type and
-- TopoGeom requested geometry type
IF layertype != 4 and layertype != tg_type THEN
RAISE EXCEPTION 'A Layer of type % cannot contain a TopoGeometry of type %', layertype, tg_type;
END IF;
-- Set layer id and type in return object
ret.layer_id = layer_id;
ret.type = tg_type;
--
-- Get new TopoGeo id from sequence
--
FOR rec IN EXECUTE 'SELECT nextval(' ||
quote_literal(
quote_ident(toponame) || '.topogeo_s_' || layer_id
) || ')'
LOOP
ret.id = rec.nextval;
END LOOP;
-- Loop over outer dimension
i = array_lower(tg_objs, 1);
LOOP
obj_id = tg_objs[i][1];
obj_type = tg_objs[i][2];
-- Elements of type 0 represent emptiness, just skip them
IF obj_type = 0 THEN
IF obj_id != 0 THEN
RAISE EXCEPTION 'Malformed empty topo element {0,%} -- id must be 0 as well', obj_id;
END IF;
ELSE
IF layerlevel = 0 THEN -- array specifies lower-level objects
IF tg_type != 4 and tg_type != obj_type THEN
RAISE EXCEPTION 'A TopoGeometry of type % cannot contain topology elements of type %', tg_type, obj_type;
END IF;
ELSE -- array specifies lower-level topogeometries
IF obj_type != layerchild THEN
RAISE EXCEPTION 'TopoGeom element layer do not match TopoGeom child layer';
END IF;
-- TODO: verify that the referred TopoGeometry really
-- exists in the relation table ?
END IF;
--RAISE NOTICE 'obj:% type:% id:%', i, obj_type, obj_id;
--
-- Insert record into the Relation table
--
EXECUTE 'INSERT INTO '||quote_ident(toponame)
|| '.relation(topogeo_id, layer_id, '
'element_id,element_type) '
' VALUES ('||ret.id
||','||ret.layer_id
|| ',' || obj_id || ',' || obj_type || ');';
END IF;
i = i+1;
IF i > array_upper(tg_objs, 1) THEN
EXIT;
END IF;
END LOOP;
RETURN ret;
END
$$
LANGUAGE 'plpgsql' VOLATILE STRICT;
--} CreateTopoGeom(toponame,topogeom_type, layer_id, TopoElementArray)
--{
-- CreateTopoGeom(topology_name, topogeom_type, layer_id) - creates the empty topogeom
-- Availability: 1.1.0
CREATE OR REPLACE FUNCTION topology.CreateTopoGeom(toponame varchar, tg_type integer, layer_id integer)
RETURNS topology.TopoGeometry
AS
$$
SELECT topology.CreateTopoGeom($1,$2,$3,'{{0,0}}');
$$ LANGUAGE 'sql' VOLATILE STRICT;
--} CreateTopoGeom(toponame, topogeom_type, layer_id)
--{
-- GetTopologyName(topology_id)
--
-- TODO: rewrite in SQL, as a wrapper around findTopology
--
-- Availability: 1.1.0
--
CREATE OR REPLACE FUNCTION topology.GetTopologyName(topoid integer)
RETURNS varchar
AS
$$
DECLARE
ret varchar;
BEGIN
SELECT name FROM topology.topology into ret
WHERE id = topoid;
RETURN ret;
END
$$
LANGUAGE 'plpgsql' STABLE STRICT;
--} GetTopologyName(topoid)
--{
-- GetTopologyId(toponame)
--
-- TODO: rewrite in SQL, as a wrapper around findTopology
--
-- Availability: 1.1.0
--
CREATE OR REPLACE FUNCTION topology.GetTopologyId(toponame varchar)
RETURNS integer
AS
$$
DECLARE
ret integer;
BEGIN
SELECT id INTO ret
FROM topology.topology WHERE name = toponame;
IF NOT FOUND THEN
RAISE EXCEPTION 'Topology % does not exist', quote_literal(toponame);
END IF;
RETURN ret;
END
$$
LANGUAGE 'plpgsql' STABLE STRICT;
--} GetTopologyId(toponame)
--{
-- GetTopologySRID(toponame)
--
CREATE OR REPLACE FUNCTION topology.GetTopologySRID(toponame varchar)
RETURNS integer
AS $$
SELECT SRID FROM topology.topology WHERE name = $1;
$$ LANGUAGE 'sql' STABLE STRICT;
--} GetTopologySRID(toponame)
--{
-- GetTopoGeomElementArray(toponame, layer_id, topogeom_id)
-- GetTopoGeomElementArray(TopoGeometry)
--
-- Returns a set of element_id,element_type
--
-- Availability: 1.1.0
--
CREATE OR REPLACE FUNCTION topology.GetTopoGeomElementArray(toponame varchar, layer_id integer, tgid integer)
RETURNS topology.TopoElementArray
AS
$$
DECLARE
rec RECORD;
tg_objs varchar := '{';
i integer;
query text;
BEGIN
query = 'SELECT * FROM topology.GetTopoGeomElements('
|| quote_literal(toponame) || ','
|| quote_literal(layer_id) || ','
|| quote_literal(tgid)
|| ') as obj ORDER BY obj';
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'Query: %', query;
#endif
-- TODO: why not using array_agg here ?
i = 1;
FOR rec IN EXECUTE query
LOOP
IF i > 1 THEN
tg_objs = tg_objs || ',';
END IF;
tg_objs = tg_objs || '{'
|| rec.obj[1] || ',' || rec.obj[2]
|| '}';
i = i+1;
END LOOP;
tg_objs = tg_objs || '}';
RETURN tg_objs;
END;
$$
LANGUAGE 'plpgsql' STABLE STRICT;
CREATE OR REPLACE FUNCTION topology.GetTopoGeomElementArray(tg topology.TopoGeometry)
RETURNS topology.TopoElementArray
AS
$$
DECLARE
toponame varchar;
BEGIN
toponame = topology.GetTopologyName(tg.topology_id);
RETURN topology.GetTopoGeomElementArray(toponame, tg.layer_id, tg.id);
END;
$$
LANGUAGE 'plpgsql' STABLE STRICT;
--} GetTopoGeomElementArray()
--{
-- GetTopoGeomElements(toponame, layer_id, topogeom_id)
-- GetTopoGeomElements(TopoGeometry)
--
-- Returns a set of element_id,element_type
--
CREATE OR REPLACE FUNCTION topology.GetTopoGeomElements(toponame varchar, layerid integer, tgid integer)
RETURNS SETOF topology.TopoElement
AS
$$
DECLARE
ret topology.TopoElement;
rec RECORD;
rec2 RECORD;
query text;
query2 text;
lyr RECORD;
ok bool;
topoid INTEGER;
BEGIN
-- Get topology id
SELECT id INTO topoid
FROM topology.topology WHERE name = toponame;
IF NOT FOUND THEN
RAISE EXCEPTION 'Topology % does not exist', quote_literal(toponame);
END IF;
-- Get layer info
ok = false;
FOR rec IN EXECUTE 'SELECT * FROM topology.layer '
' WHERE layer_id = $1 AND topology_id = $2'
USING layerid, topoid
LOOP
lyr = rec;
ok = true;
END LOOP;
IF NOT ok THEN
RAISE EXCEPTION 'Layer % does not exist', layerid;
END IF;
query = 'SELECT abs(element_id) as element_id, element_type FROM '
|| quote_ident(toponame) || '.relation WHERE '
' layer_id = ' || layerid
|| ' AND topogeo_id = ' || quote_literal(tgid)
|| ' ORDER BY element_type, element_id';
--RAISE NOTICE 'Query: %', query;
FOR rec IN EXECUTE query
LOOP
IF lyr.level > 0 THEN
query2 = 'SELECT * from topology.GetTopoGeomElements('
|| quote_literal(toponame) || ','
|| rec.element_type
|| ','
|| rec.element_id
|| ') as ret;';
--RAISE NOTICE 'Query2: %', query2;
FOR rec2 IN EXECUTE query2
LOOP
RETURN NEXT rec2.ret;
END LOOP;
ELSE
ret = '{' || rec.element_id || ',' || rec.element_type || '}';
RETURN NEXT ret;
END IF;
END LOOP;
RETURN;
END;
$$
LANGUAGE 'plpgsql' STABLE STRICT;
CREATE OR REPLACE FUNCTION topology.GetTopoGeomElements(tg topology.TopoGeometry)
RETURNS SETOF topology.TopoElement
AS
$$
DECLARE
toponame varchar;
rec RECORD;
BEGIN
toponame = topology.GetTopologyName(tg.topology_id);
FOR rec IN SELECT * FROM topology.GetTopoGeomElements(toponame,
tg.layer_id,tg.id) as ret
LOOP
RETURN NEXT rec.ret;
END LOOP;
RETURN;
END;
$$
LANGUAGE 'plpgsql' STABLE STRICT;
--} GetTopoGeomElements()
--{
-- Geometry(TopoGeometry)
--
-- Construct a Geometry from a TopoGeometry.
--
-- }{
CREATE OR REPLACE FUNCTION topology.Geometry(topogeom topology.TopoGeometry)
RETURNS Geometry
AS $BODY$
DECLARE
toponame varchar;
toposrid INT;
geom geometry;
elements_count INT;
rec RECORD;
plyr RECORD;
clyr RECORD;
sql TEXT;
BEGIN
-- Get topology name
SELECT name, srid FROM topology.topology
WHERE id = topogeom.topology_id
INTO toponame, toposrid;
IF toponame IS NULL THEN
RAISE EXCEPTION 'Invalid TopoGeometry (unexistent topology id %)', topogeom.topology_id;
END IF;
-- Get layer info
SELECT * FROM topology.layer
WHERE topology_id = topogeom.topology_id
AND layer_id = topogeom.layer_id
INTO plyr;
IF plyr IS NULL THEN
RAISE EXCEPTION 'Could not find TopoGeometry layer % in topology %', topogeom.layer_id, topogeom.topology_id;
END IF;
--
-- If this feature layer is on any level > 0 we will
-- compute the topological union of all child features
-- in fact recursing.
--
IF plyr.level > 0 THEN -- {
-- Get child layer info
SELECT * FROM topology.layer WHERE layer_id = plyr.child_id
AND topology_id = topogeom.topology_id
INTO clyr;
IF clyr IS NULL THEN
RAISE EXCEPTION 'Invalid layer % in topology % (unexistent child layer %)', topogeom.layer_id, topogeom.topology_id, plyr.child_id;
END IF;
sql := 'SELECT st_multi(st_union(topology.Geometry('
|| quote_ident(clyr.feature_column)
|| '))) as geom FROM '
|| quote_ident(clyr.schema_name) || '.'
|| quote_ident(clyr.table_name)
|| ', ' || quote_ident(toponame) || '.relation pr'
' WHERE '
' pr.topogeo_id = ' || topogeom.id
|| ' AND '
' pr.layer_id = ' || topogeom.layer_id
|| ' AND '
' id('||quote_ident(clyr.feature_column)
|| ') = pr.element_id '
' AND '
'layer_id('||quote_ident(clyr.feature_column)
|| ') = pr.element_type ';
--RAISE DEBUG '%', query;
EXECUTE sql INTO geom;
ELSIF topogeom.type = 3 THEN -- [multi]polygon -- }{
sql := format(
$$
SELECT
count(element_id),
ST_Multi(
ST_Union(
topology.ST_GetFaceGeometry(
%1$L,
element_id
)
)
) as g
FROM %1$I.relation
WHERE topogeo_id = %2$L
AND layer_id = %3$L
AND element_type = 3
$$,
toponame,
topogeom.id,
topogeom.layer_id
);
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'Query: %', sql;
#endif
EXECUTE sql INTO elements_count, geom;
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'TopoGeometry of type 3 (areal)'
' in topo with srid % defined by % face elements'
' casted to geometry with srid %',
toposrid,
elements_count,
ST_Srid(geom)
;
#endif
ELSIF topogeom.type = 2 THEN -- [multi]line -- }{
sql := format(
$$
SELECT
st_multi(
ST_LineMerge(
ST_Collect(
CASE
WHEN r.element_id > 0 THEN
e.geom
ELSE
ST_Reverse(e.geom)
END
)
)
) as g
FROM %1$I.edge e, %1$I.relation r
WHERE r.topogeo_id = id($1)
AND r.layer_id = layer_id($1)
AND r.element_type = 2
AND abs(r.element_id) = e.edge_id
$$,
toponame
);
EXECUTE sql USING topogeom INTO geom;
ELSIF topogeom.type = 1 THEN -- [multi]point -- }{
sql :=
'SELECT st_multi(st_union(n.geom)) as g FROM '
|| quote_ident(toponame) || '.node n, '
|| quote_ident(toponame) || '.relation r '
' WHERE r.topogeo_id = ' || topogeom.id
|| ' AND r.layer_id = ' || topogeom.layer_id
|| ' AND r.element_type = 1 '
' AND r.element_id = n.node_id';
EXECUTE sql INTO geom;
ELSIF topogeom.type = 4 THEN -- mixed collection -- }{
sql := 'WITH areas AS ( SELECT ST_Union('
'topology.ST_GetFaceGeometry('
|| quote_literal(toponame) || ','
|| 'element_id)) as g FROM '
|| quote_ident(toponame)
|| '.relation WHERE topogeo_id = '
|| topogeom.id || ' AND layer_id = '
|| topogeom.layer_id || ' AND element_type = 3), '
'lines AS ( SELECT ST_LineMerge(ST_Collect(e.geom)) as g FROM '
|| quote_ident(toponame) || '.edge e, '
|| quote_ident(toponame) || '.relation r '
' WHERE r.topogeo_id = ' || topogeom.id
|| ' AND r.layer_id = ' || topogeom.layer_id
|| ' AND r.element_type = 2 '
' AND abs(r.element_id) = e.edge_id ), '
' points as ( SELECT st_union(n.geom) as g FROM '
|| quote_ident(toponame) || '.node n, '
|| quote_ident(toponame) || '.relation r '
' WHERE r.topogeo_id = ' || topogeom.id
|| ' AND r.layer_id = ' || topogeom.layer_id
|| ' AND r.element_type = 1 '
' AND r.element_id = n.node_id ), '
' un as ( SELECT g FROM areas UNION ALL SELECT g FROM lines '
' UNION ALL SELECT g FROM points ) '
'SELECT ST_Multi(ST_Collect(g)) FROM un';
EXECUTE sql INTO geom;
ELSE -- }{
RAISE EXCEPTION 'Invalid TopoGeometries (unknown type %)', topogeom.type;
END IF; -- }
IF geom IS NULL THEN
IF topogeom.type = 3 THEN -- [multi]polygon
geom := 'MULTIPOLYGON EMPTY';
ELSIF topogeom.type = 2 THEN -- [multi]line
geom := 'MULTILINESTRING EMPTY';
ELSIF topogeom.type = 1 THEN -- [multi]point
geom := 'MULTIPOINT EMPTY';
ELSE
geom := 'GEOMETRYCOLLECTION EMPTY';
END IF;
geom := ST_SetSRID(geom, toposrid);
END IF;
RETURN geom;
END
$BODY$
LANGUAGE 'plpgsql' VOLATILE STRICT;
--} Geometry(TopoGeometry)
-- 7.3+ explicit cast
CREATE CAST (topology.TopoGeometry AS Geometry) WITH FUNCTION topology.Geometry(topology.TopoGeometry) AS IMPLICIT;
--{
-- TopoElement(TopoGeometry)
--
-- Construct a TopoElement from a TopoGeometry.
--
-- Availability: 3.4.0
-- }{
CREATE OR REPLACE FUNCTION topology.TopoElement(topo topology.TopoGeometry)
RETURNS topology.TopoElement
LANGUAGE sql
COST 1
IMMUTABLE PARALLEL SAFE
AS $$SELECT ARRAY[topo.id,topo.layer_id]::topology.topoelement;$$;
--} TopoElement(TopoGeometry)
--{
-- TopoGeometry(int[])
--
-- Cast a TopoGeometry to a TopoElement.
--
-- Availability: 3.4.0
-- }{
CREATE CAST (topogeometry AS int[]) WITH FUNCTION TopoElement(topogeometry) AS ASSIGNMENT;
--} TopoGeometry(int[])
--{
-- DropTopology(name)
--
-- Drops a topology schema getting rid of every dependent object.
--
-- Availability: 1.1.0
--
CREATE OR REPLACE FUNCTION topology.DropTopology(atopology varchar)
RETURNS text
AS
$$
DECLARE
topoid integer;
rec RECORD;
sql TEXT;
toposchema REGNAMESPACE;
deferred_constraints TEXT[];
BEGIN
-- Get topology id
SELECT id INTO topoid
FROM topology.topology WHERE name = atopology;
IF NOT FOUND THEN
RAISE EXCEPTION 'Topology % does not exist', quote_literal(atopology);
END IF;
RAISE NOTICE 'Dropping all layers from topology % (%)',
quote_literal(atopology), topoid;
-- Drop all layers in the topology
sql := 'SELECT * FROM topology.layer WHERE topology_id = $1';
FOR rec IN EXECUTE sql USING topoid
LOOP
sql := format(
'SELECT topology.DropTopoGeometryColumn(%L, %L, %L)',
rec.schema_name, rec.table_name, rec.feature_column
);
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'Executing: %', sql;
#endif
EXECUTE sql;
END LOOP;
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'Deleting record from topology.topology';
#endif
-- Delete record from topology.topology
sql := 'DELETE FROM topology.topology WHERE id = $1';
EXECUTE sql USING topoid;
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'Query pg_namespace';
#endif
-- Drop the schema (if it exists)
SELECT oid FROM pg_namespace WHERE text(nspname) = atopology
INTO toposchema;
IF toposchema IS NOT NULL THEN --{
-- Give immediate execution to pending constraints
-- in the topology schema.
--
-- See https://trac.osgeo.org/postgis/ticket/5115
SELECT array_agg(format('%I.%I', atopology, conname))
FROM pg_constraint c
WHERE connamespace = toposchema AND condeferred
INTO deferred_constraints;
IF deferred_constraints IS NOT NULL THEN --{
sql := format(
'SET constraints %s IMMEDIATE',
array_to_string(deferred_constraints, ',')
);
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'Executing: %', sql;
#endif
EXECUTE sql;
END IF; --}
sql := format('DROP SCHEMA %I CASCADE', atopology);
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'Executing: %', sql;
#endif
EXECUTE sql;
END IF; --}
RETURN format('Topology %L dropped', atopology);
END
$$
LANGUAGE 'plpgsql' VOLATILE STRICT;
--} DropTopology
-- Spatial predicates
#include "sql/predicates.sql.in"
-- Querying
#include "sql/query/getnodebypoint.sql.in"
#include "sql/query/getedgebypoint.sql.in"
#include "sql/query/getfacebypoint.sql.in"
#include "sql/query/GetFaceContainingPoint.sql.in"
-- Populating
#include "sql/populate.sql.in"
#include "sql/polygonize.sql.in"
-- TopoElement
#include "sql/topoelement/topoelement_agg.sql.in"
-- TopoGeometry
#include "sql/topogeometry/type.sql.in"
#include "sql/topogeometry/srid.sql.in"
#include "sql/topogeometry/cleartopogeom.sql.in"
#include "sql/topogeometry/simplify.sql.in"
#include "sql/topogeometry/totopogeom.sql.in"
#include "sql/topogeometry/topogeom_edit.sql.in"
-- Exports
#include "sql/export/gml.sql.in"
#include "sql/export/TopoJSON.sql.in"
--=} POSTGIS-SPECIFIC block
-- SQL/MM block
#include "sql/sqlmm.sql.in"
-- The following files needs getfaceedges_returntype, defined in sqlmm.sql
#include "sql/query/GetRingEdges.sql.in"
#include "sql/query/GetNodeEdges.sql.in"
--general management --
#include "sql/manage/ManageHelper.sql.in"
#include "sql/manage/AddTopoGeometryColumn.sql.in"
#include "sql/manage/RenameTopoGeometryColumn.sql.in"
#include "sql/manage/CreateTopology.sql.in"
#include "sql/manage/TopologySummary.sql.in"
#include "sql/manage/CopyTopology.sql.in"
#include "sql/manage/FindTopology.sql.in"
#include "sql/manage/FindLayer.sql.in"
#include "sql/manage/RenameTopology.sql.in"
#include "sql/manage/ValidateTopology.sql.in"
#include "sql/manage/ValidateTopologyRelation.sql.in"
-- Cleanup functions
#include "sql/cleanup/RemoveUnusedPrimitives.sql.in"
CREATE OR REPLACE FUNCTION topology.postgis_topology_scripts_installed() RETURNS text
AS _POSTGIS_SQL_SELECT_POSTGIS_SCRIPTS_VERSION
LANGUAGE 'sql' IMMUTABLE;
-- Make sure topology is in database search path --
SELECT topology.AddToSearchPath('topology');
-- Make metadata tables publically visible
-- See https://trac.osgeo.org/postgis/ticket/4575
GRANT SELECT ON topology.topology TO PUBLIC;
GRANT SELECT ON topology.layer TO PUBLIC;
-- See https://trac.osgeo.org/postgis/ticket/5124
GRANT USAGE ON SCHEMA topology TO PUBLIC;
COMMIT;