postgis/topology/sql/sqlmm.sql.in

576 lines
15 KiB
MySQL

-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- PostGIS - Spatial Types for PostgreSQL
-- http://postgis.net
--
-- Copyright (C) 2010-2015 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>
--
--
/*#define POSTGIS_TOPOLOGY_DEBUG 1*/
--={ ----------------------------------------------------------------
-- SQL/MM block
--
-- This part contains function in the SQL/MM specification
--
---------------------------------------------------------------------
--
-- Type returned by ST_GetFaceEdges
--
-- Availability: 1.1.0
--
CREATE TYPE topology.GetFaceEdges_ReturnType AS (
sequence integer,
edge integer
);
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.5
--
-- ST_GetFaceEdges(atopology, aface)
--
-- Availability: 1.1.0
--
CREATE OR REPLACE FUNCTION topology.ST_GetFaceEdges(toponame varchar, face_id integer)
RETURNS SETOF topology.GetFaceEdges_ReturnType AS
'MODULE_PATHNAME', 'ST_GetFaceEdges'
LANGUAGE 'c' STABLE;
--} ST_GetFaceEdges
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.10
--
-- ST_NewEdgeHeal(atopology, anedge, anotheredge)
--
-- Not in the specs:
-- * Refuses to heal two edges if any of the two is closed
-- * Raise an exception when trying to heal an edge with itself
-- * Raise an exception if any TopoGeometry is defined by only one
-- of the two edges
-- * Update references in the Relation table.
--
CREATE OR REPLACE FUNCTION topology.ST_NewEdgeHeal(toponame varchar, e1id integer, e2id integer)
RETURNS int AS
'MODULE_PATHNAME','ST_NewEdgeHeal'
LANGUAGE 'c' VOLATILE;
--} ST_NewEdgeHeal
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.11
--
-- ST_ModEdgeHeal(atopology, anedge, anotheredge)
--
-- Not in the specs:
-- * Returns the id of the node being removed
-- * Refuses to heal two edges if any of the two is closed
-- * Raise an exception when trying to heal an edge with itself
-- * Raise an exception if any TopoGeometry is defined by only one
-- of the two edges
-- * Update references in the Relation table.
-- {
CREATE OR REPLACE FUNCTION topology.ST_ModEdgeHeal(toponame varchar, e1id integer, e2id integer)
RETURNS int AS
'MODULE_PATHNAME','ST_ModEdgeHeal'
LANGUAGE 'c' VOLATILE;
--} ST_ModEdgeHeal
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.14
--
-- ST_RemEdgeNewFace(atopology, anedge)
--
-- Not in the specs:
-- * Raise an exception if any TopoGeometry is defined by only one
-- of the two faces that will dissolve.
-- * Raise an exception if any TopoGeometry is defined by
-- the edge being removed.
-- * Properly set containg_face on nodes that remains isolated by the drop
-- * Update containg_face for isolated nodes in the dissolved faces
-- * Update references in the Relation table
--
-- }{
CREATE OR REPLACE FUNCTION topology.ST_RemEdgeNewFace(toponame varchar, e1id integer)
RETURNS int AS
'MODULE_PATHNAME','ST_RemEdgeNewFace'
LANGUAGE 'c' VOLATILE;
--} ST_RemEdgeNewFace
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.15
--
-- ST_RemEdgeModFace(atopology, anedge)
--
-- Not in the specs:
-- * Raise an exception if any TopoGeometry is defined by only one
-- of the two faces that will dissolve.
-- * Raise an exception if any TopoGeometry is defined by
-- the edge being removed.
-- * Properly set containg_face on nodes that remains isolated by the drop
-- * Update containg_face for isolated nodes in the dissolved faces
-- * Update references in the Relation table
-- * Return id of the face taking up the removed edge space
--
-- }{
CREATE OR REPLACE FUNCTION topology.ST_RemEdgeModFace(toponame varchar, e1id integer)
RETURNS int AS
'MODULE_PATHNAME','ST_RemEdgeModFace'
LANGUAGE 'c' VOLATILE;
--} ST_RemEdgeModFace
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.16
--
-- ST_GetFaceGeometry(atopology, aface)
--
CREATE OR REPLACE FUNCTION topology.ST_GetFaceGeometry(toponame varchar, aface integer)
RETURNS GEOMETRY AS
'MODULE_PATHNAME', 'ST_GetFaceGeometry'
LANGUAGE 'c' STABLE;
--} ST_GetFaceGeometry
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.1
--
-- ST_AddIsoNode(atopology, aface, apoint)
--
-- Availability: 1.1.0
--
CREATE OR REPLACE FUNCTION topology.ST_AddIsoNode(atopology varchar, aface integer, apoint geometry)
RETURNS INTEGER AS
'MODULE_PATHNAME','ST_AddIsoNode'
LANGUAGE 'c' VOLATILE;
--} ST_AddIsoNode
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.2
--
-- ST_MoveIsoNode(atopology, anode, apoint)
--
CREATE OR REPLACE FUNCTION topology.ST_MoveIsoNode(atopology character varying, anode integer, apoint geometry)
RETURNS text AS
'MODULE_PATHNAME','ST_MoveIsoNode'
LANGUAGE 'c' VOLATILE;
--} ST_MoveIsoNode
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.3
--
-- ST_RemoveIsoNode(atopology, anode)
--
-- Availability: 1.1.0
--
CREATE OR REPLACE FUNCTION topology.ST_RemoveIsoNode(atopology varchar, anode integer)
RETURNS TEXT AS
'MODULE_PATHNAME','ST_RemoveIsoNode'
LANGUAGE 'c' VOLATILE;
--} ST_RemoveIsoNode
--{
-- According to http://trac.osgeo.org/postgis/ticket/798
-- ST_RemoveIsoNode was renamed to ST_RemIsoNode in the final ISO
-- document
--
CREATE OR REPLACE FUNCTION topology.ST_RemIsoNode(varchar, integer)
RETURNS TEXT AS
'MODULE_PATHNAME','ST_RemoveIsoNode'
LANGUAGE 'c' VOLATILE;
--} ST_RemIsoNode
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.7
--
-- ST_RemoveIsoEdge(atopology, anedge)
--
-- Availability: 1.1.0
--
CREATE OR REPLACE FUNCTION topology.ST_RemoveIsoEdge(atopology varchar, anedge integer)
RETURNS TEXT AS
'MODULE_PATHNAME','ST_RemIsoEdge'
LANGUAGE 'c' VOLATILE;
--} ST_RemoveIsoEdge
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.8
--
-- ST_NewEdgesSplit(atopology, anedge, apoint)
--
-- Not in the specs:
-- * Update references in the Relation table.
--
-- Availability: 1.1.0
--
CREATE OR REPLACE FUNCTION topology.ST_NewEdgesSplit(atopology varchar, anedge integer, apoint geometry)
RETURNS INTEGER AS
'MODULE_PATHNAME','ST_NewEdgesSplit'
LANGUAGE 'c' VOLATILE;
--} ST_NewEdgesSplit
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.9
--
-- ST_ModEdgeSplit(atopology, anedge, apoint)
--
-- Not in the specs:
-- * Update references in the Relation table.
--
-- Availability: 2.0.0
-- Replaces ST_ModEdgesSplit(varchar, integer, geometry) deprecated in 2.0.0
--
CREATE OR REPLACE FUNCTION topology.ST_ModEdgeSplit(atopology varchar, anedge integer, apoint geometry)
RETURNS INTEGER AS
'MODULE_PATHNAME','ST_ModEdgeSplit'
LANGUAGE 'c' VOLATILE;
--} ST_ModEdgesSplit
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.4
--
-- ST_AddIsoEdge(atopology, anode, anothernode, acurve)
--
-- Not in the specs:
-- * Reset containing_face for starting and ending point,
-- as they stop being isolated nodes
-- * Refuse to add a closed edge, as it would not be isolated
-- (ie: would create a ring)
--
-- Availability: 1.1.0
--
-- }{
--
CREATE OR REPLACE FUNCTION topology.ST_AddIsoEdge(atopology varchar, anode integer, anothernode integer, acurve geometry)
RETURNS INTEGER AS
'MODULE_PATHNAME','ST_AddIsoEdge'
LANGUAGE 'c' VOLATILE;
--} ST_AddIsoEdge
-- Internal function used by ST_ChangeEdgeGeom to compare
-- adjacent edges of an edge endpoint
--
-- @param anode the node to use edge end star of
-- @param anedge the directed edge to get adjacents from
-- if positive `anode' is assumed to be its start node
-- if negative `anode' is assumed to be its end node
--
-- @todo DROP, NOT NEEDED ANYMORE (might need to go in some _drop, I guess)
-- {
CREATE OR REPLACE FUNCTION topology._ST_AdjacentEdges(atopology varchar, anode integer, anedge integer)
RETURNS integer[] AS
$$
DECLARE
ret integer[];
BEGIN
WITH edgestar AS (
SELECT *, count(*) over () AS cnt
FROM topology.GetNodeEdges(atopology, anode)
)
SELECT ARRAY[ (
SELECT p.edge AS prev FROM edgestar p
WHERE p.sequence = CASE WHEN m.sequence-1 < 1 THEN cnt
ELSE m.sequence-1 END
), (
SELECT p.edge AS prev FROM edgestar p WHERE p.sequence = ((m.sequence)%cnt)+1
) ]
FROM edgestar m
WHERE edge = anedge
INTO ret;
RETURN ret;
END
$$
LANGUAGE 'plpgsql' STABLE;
--}
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.6
--
-- ST_ChangeEdgeGeom(atopology, anedge, acurve)
--
-- Not in the specs:
-- * Raise an exception if given a non-existent edge
-- * Raise an exception if movement is not topologically isomorphic
--
-- }{
CREATE OR REPLACE FUNCTION topology.ST_ChangeEdgeGeom(atopology varchar, anedge integer, acurve geometry)
RETURNS TEXT AS
'MODULE_PATHNAME','ST_ChangeEdgeGeom'
LANGUAGE 'c' VOLATILE;
--} ST_ChangeEdgeGeom
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.12
--
-- ST_AddEdgeNewFaces(atopology, anode, anothernode, acurve)
--
-- Not in the specs:
-- * Reset containing_face for starting and ending point,
-- as they stop being isolated nodes
-- * Update references in the Relation table.
--
CREATE OR REPLACE FUNCTION topology.ST_AddEdgeNewFaces(atopology varchar, anode integer, anothernode integer, acurve geometry)
RETURNS INTEGER AS
'MODULE_PATHNAME','ST_AddEdgeNewFaces'
LANGUAGE 'c' VOLATILE;
--} ST_AddEdgeNewFaces
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.13
--
-- ST_AddEdgeModFace(atopology, anode, anothernode, acurve)
--
-- Not in the specs:
-- * Reset containing_face for starting and ending point,
-- as they stop being isolated nodes
-- * Update references in the Relation table.
--
CREATE OR REPLACE FUNCTION topology.ST_AddEdgeModFace(atopology varchar, anode integer, anothernode integer, acurve geometry)
RETURNS INTEGER AS
'MODULE_PATHNAME','ST_AddEdgeModFace'
LANGUAGE 'c' VOLATILE;
--} ST_AddEdgeModFace
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.17
--
-- ST_InitTopoGeo(atopology)
--
-- Availability: 1.1.0
--
CREATE OR REPLACE FUNCTION topology.ST_InitTopoGeo(atopology varchar)
RETURNS text
AS
$$
DECLARE
rec RECORD;
topology_id numeric;
BEGIN
IF atopology IS NULL THEN
RAISE EXCEPTION 'SQL/MM Spatial exception - null argument';
END IF;
FOR rec IN SELECT * FROM pg_namespace WHERE text(nspname) = atopology
LOOP
RAISE EXCEPTION 'SQL/MM Spatial exception - schema already exists';
END LOOP;
FOR rec IN EXECUTE 'SELECT topology.CreateTopology('
||quote_literal(atopology)|| ') as id'
LOOP
topology_id := rec.id;
END LOOP;
RETURN 'Topology-Geometry ' || quote_literal(atopology)
|| ' (id:' || topology_id || ') created.';
END
$$
LANGUAGE 'plpgsql' VOLATILE;
--} ST_InitTopoGeo
--{
-- Topo-Geo and Topo-Net 3: Routine Details
-- X.3.18
--
-- ST_CreateTopoGeo(atopology, acollection)
--}{
CREATE OR REPLACE FUNCTION topology.ST_CreateTopoGeo(atopology varchar, acollection geometry)
RETURNS text
AS
$$
DECLARE
typ char(4);
rec RECORD;
ret int;
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;
-- 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;
-- 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 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 '
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; -- }
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'Noding input linework';
#endif
--
-- 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 ALL
SELECT ST_Boundary(geom) FROM components
WHERE ST_Dimension(geom) = 2
) as linework INTO STRICT nodededges;
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'Computed % noded edges', ST_NumGeometries(nodededges);
#endif
--
-- Linemerge the resulting edges, to reduce the working set
-- NOTE: this is more of a workaround for GEOS splitting overlapping
-- lines to each of the segments.
--
SELECT ST_LineMerge(nodededges) INTO STRICT nodededges;
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'Merged edges: %', ST_NumGeometries(nodededges);
#endif
--
-- Collect input points and input lines endpoints
--
WITH components AS ( SELECT geom FROM ST_Dump(acollection) )
SELECT ST_Union(geom) FROM (
SELECT geom FROM components
WHERE ST_Dimension(geom) = 0
UNION ALL
SELECT ST_Boundary(geom) FROM components
WHERE ST_Dimension(geom) = 1
) as nodes INTO STRICT points;
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'Collected % input points', ST_NumGeometries(points);
#endif
--
-- Further split edges by points, if needed
--
IF points IS NOT NULL THEN
nodededges := ST_Split(nodededges, points);
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'Noded edges became % after point-split',
ST_NumGeometries(nodededges);
#endif
END IF; -- points is not null
--
-- 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 ALL
SELECT ST_EndPoint(geom) FROM edges
) as endpoints INTO points;
#ifdef POSTGIS_TOPOLOGY_DEBUG
RAISE DEBUG 'Total nodes count: %', ST_NumGeometries(points);
#endif
--
-- 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
PERFORM topology.ST_AddIsoNode(atopology, 0, rec.geom);
END LOOP;
FOR rec IN SELECT geom FROM ST_Dump(nodededges)
LOOP
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';
END
$$
LANGUAGE 'plpgsql' VOLATILE;
--} ST_CreateTopoGeo
--=} SQL/MM block