495 lines
16 KiB
PL/PgSQL
495 lines
16 KiB
PL/PgSQL
-- Data Observatory -- Welcome to the Future
|
|
-- These Data Observatory functions provide access to boundary polyons (and
|
|
-- their ids) such as those available through the US Census Tiger, Who's on
|
|
-- First, the Spanish Census, and so on
|
|
|
|
|
|
-- OBS_GetBoundary
|
|
--
|
|
-- Returns the boundary polygon(s) that overlap with the input point geometry.
|
|
-- From an input point geometry, find the boundary which intersects with the
|
|
-- centroid of the input geometry
|
|
-- Inputs:
|
|
-- geom geometry: input point geometry
|
|
-- boundary_id text: source id of boundaries
|
|
-- see function OBS_ListGeomColumns for all avaiable
|
|
-- boundary ids
|
|
-- time_span text: time span that the geometries were collected (optional)
|
|
--
|
|
-- Output:
|
|
-- boundary geometry: geometry boundary that intersects with geom, is at the
|
|
-- resolution requested with boundary_id, and time_span
|
|
--
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetBoundary(
|
|
geom geometry(Geometry, 4326),
|
|
boundary_id text,
|
|
time_span text DEFAULT NULL)
|
|
RETURNS geometry(Geometry, 4326)
|
|
AS $$
|
|
DECLARE
|
|
boundary geometry(Geometry, 4326);
|
|
target_table text;
|
|
BEGIN
|
|
|
|
-- TODO: Check if SRID = 4326, if not transform?
|
|
|
|
-- if not a point, raise error
|
|
IF ST_GeometryType(geom) != 'ST_Point'
|
|
THEN
|
|
RAISE EXCEPTION 'Invalid geometry type (%), expecting ''ST_Point''', ST_GeometryType(geom);
|
|
END IF;
|
|
|
|
-- choose appropriate table based on time_span
|
|
IF time_span IS NULL
|
|
THEN
|
|
SELECT x.target_tables INTO target_table
|
|
FROM cdb_observatory._OBS_SearchTables(boundary_id,
|
|
time_span) As x(target_tables,
|
|
timespans)
|
|
ORDER BY x.timespans DESC
|
|
LIMIT 1;
|
|
ELSE
|
|
-- TODO: modify for only one table returned instead of arbitrarily choosing
|
|
-- one with LIMIT 1 (could be conflict between clipped vs non-clipped
|
|
-- boundaries in the metadata tables)
|
|
SELECT x.target_tables INTO target_table
|
|
FROM cdb_observatory._OBS_SearchTables(boundary_id,
|
|
time_span) As x(target_tables,
|
|
timespans)
|
|
WHERE x.timespans = time_span
|
|
LIMIT 1;
|
|
END IF;
|
|
|
|
-- if no tables are found, raise notice and return null
|
|
IF target_table IS NULL
|
|
THEN
|
|
RAISE NOTICE 'No boundaries found for ''%'' in ''%''', ST_AsText(geom), boundary_id;
|
|
RETURN NULL::geometry;
|
|
END IF;
|
|
|
|
RAISE NOTICE 'target_table: %', target_table;
|
|
|
|
-- return the first boundary in intersections
|
|
EXECUTE format(
|
|
'SELECT t.the_geom
|
|
FROM observatory.%s As t
|
|
WHERE ST_Intersects($1, t.the_geom)
|
|
LIMIT 1', target_table)
|
|
INTO boundary
|
|
USING geom;
|
|
|
|
RETURN boundary;
|
|
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- OBS_GetBoundaryId
|
|
--
|
|
-- retrieves the boundary identifier (e.g., '36047' = Kings County/Brooklyn, NY)
|
|
-- corresponding to the location geom and boundary types (e.g.,
|
|
-- us.census.tiger.county)
|
|
|
|
-- Inputs:
|
|
-- geom geometry: location where the boundary is requested to overlap with
|
|
-- boundary_id text: source id of boundaries (e.g., us.census.tiger.county)
|
|
-- see function OBS_ListGeomColumns for all avaiable
|
|
-- boundary ids
|
|
-- time_span text: time span that the geometries were collected (optional)
|
|
--
|
|
-- Output:
|
|
-- geometry_id text: identifier of the geometry which overlaps with the input
|
|
-- point geom in the table corresponding to boundary_id and
|
|
-- time_span
|
|
--
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetBoundaryId(
|
|
geom geometry(Geometry, 4326),
|
|
boundary_id text,
|
|
time_span text DEFAULT NULL
|
|
)
|
|
RETURNS text
|
|
AS $$
|
|
DECLARE
|
|
output_id text;
|
|
target_table text;
|
|
BEGIN
|
|
|
|
-- If not point, raise error
|
|
IF ST_GeometryType(geom) != 'ST_Point'
|
|
THEN
|
|
RAISE EXCEPTION 'Error: Invalid geometry type (%), expecting ''ST_Point''', ST_GeometryType(geom);
|
|
END IF;
|
|
|
|
-- choose appropriate table based on time_span
|
|
IF time_span IS NULL
|
|
THEN
|
|
SELECT x.target_tables INTO target_table
|
|
FROM cdb_observatory._OBS_SearchTables(boundary_id,
|
|
time_span) As x(target_tables,
|
|
timespans)
|
|
ORDER BY x.timespans DESC
|
|
LIMIT 1;
|
|
ELSE
|
|
SELECT x.target_tables INTO target_table
|
|
FROM cdb_observatory._OBS_SearchTables(boundary_id,
|
|
time_span) As x(target_tables,
|
|
timespans)
|
|
WHERE x.timespans = time_span
|
|
LIMIT 1;
|
|
END IF;
|
|
|
|
-- if no tables are found, raise error
|
|
IF target_table IS NULL
|
|
THEN
|
|
RAISE NOTICE 'Error: No boundaries found for ''%''', boundary_id;
|
|
RETURN NULL::text;
|
|
END IF;
|
|
|
|
RAISE NOTICE 'target_table: %', target_table;
|
|
|
|
-- return name of geometry id column
|
|
EXECUTE format(
|
|
'SELECT t.geoid
|
|
FROM observatory.%s As t
|
|
WHERE ST_Intersects($1, t.the_geom)
|
|
LIMIT 1', target_table)
|
|
INTO output_id
|
|
USING geom;
|
|
|
|
RETURN output_id;
|
|
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
|
|
-- OBS_GetBoundaryById
|
|
--
|
|
-- Given a geometry reference (e.g., geoid for US Census), and it's geometry
|
|
-- level (see OBS_ListGeomColumns() for all available boundary ids), give back
|
|
-- the boundary that corresponds to that geometry_id, boundary_id, and
|
|
-- time_span
|
|
|
|
-- Inputs:
|
|
-- geometry_id text: geometry id of the requested boundary
|
|
-- boundary_id text: source id of boundaries (e.g., us.census.tiger.county)
|
|
-- see function OBS_ListGeomColumns for all avaiable
|
|
-- boundary ids
|
|
-- time_span text: time span that the geometries were collected (optional)
|
|
--
|
|
-- Output:
|
|
-- boundary geometry: geometry boundary that matches geometry_id, is at the
|
|
-- resolution requested with boundary_id, and time_span
|
|
--
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetBoundaryById(
|
|
geometry_id text, -- ex: '36047'
|
|
boundary_id text, -- ex: '"us.census.tiger".county'
|
|
time_span text DEFAULT NULL -- ex: '2009'
|
|
)
|
|
RETURNS geometry(geometry, 4326)
|
|
AS $$
|
|
DECLARE
|
|
boundary geometry(geometry, 4326);
|
|
target_table text;
|
|
geoid_colname text;
|
|
geom_colname text;
|
|
BEGIN
|
|
|
|
SELECT * INTO geoid_colname, target_table, geom_colname
|
|
FROM cdb_observatory._OBS_GetGeometryMetadata(boundary_id);
|
|
|
|
RAISE NOTICE '%', target_table;
|
|
|
|
IF target_table IS NULL
|
|
THEN
|
|
RAISE NOTICE 'No geometries found';
|
|
RETURN NULL::geometry;
|
|
END IF;
|
|
|
|
-- retrieve boundary
|
|
EXECUTE
|
|
format(
|
|
'SELECT t.%s
|
|
FROM observatory.%I As t
|
|
WHERE t.%s = $1
|
|
LIMIT 1', geom_colname, target_table, geoid_colname)
|
|
INTO boundary
|
|
USING geometry_id;
|
|
|
|
RETURN boundary;
|
|
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
|
|
-- OBS_GetBoundariesByBBox
|
|
--
|
|
-- Given a bounding box (or a polygon), and it's geometry level (see
|
|
-- OBS_ListGeomColumns() for all available boundary ids), give back the
|
|
-- boundaries that are contained within the bounding box polygon and the
|
|
-- associated geometry ids
|
|
|
|
-- Inputs:
|
|
-- geom geometry: bounding box (or polygon) of the region of interest
|
|
-- boundary_id text: source id of boundaries (e.g., us.census.tiger.county)
|
|
-- see function OBS_ListGeomColumns for all avaiable
|
|
-- boundary ids
|
|
-- time_span text: time span that the geometries were collected (optional)
|
|
--
|
|
-- Output:
|
|
-- table with the following columns
|
|
-- boundary geometry: geometry boundary that is contained within the input
|
|
-- bounding box at the requested geometry level
|
|
-- with boundary_id, and time_span
|
|
-- geom_refs text: geometry identifiers (e.g., geoid for the US Census)
|
|
--
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetBoundariesByBBox(
|
|
geom geometry(Geometry, 4326),
|
|
boundary_id text,
|
|
time_span text DEFAULT NULL,
|
|
overlap_type text DEFAULT 'intersects')
|
|
RETURNS TABLE(boundary geometry, geom_refs text)
|
|
AS $$
|
|
DECLARE
|
|
boundary geometry(Geometry, 4326);
|
|
geom_colname text;
|
|
geoid_colname text;
|
|
target_table text;
|
|
BEGIN
|
|
|
|
-- check inputs
|
|
IF lower(overlap_type) NOT IN ('contains', 'intersects', 'within')
|
|
THEN
|
|
-- recognized overlap type (map to ST_Contains, ST_Intersects, and ST_Within)
|
|
RAISE EXCEPTION 'Overlap type ''%'' is not an accepted type (choose contains, within, intersects)', overlap_type;
|
|
ELSIF ST_GeometryType(geom) NOT IN ('ST_Polygon', 'ST_MultiPolygon')
|
|
THEN
|
|
RAISE EXCEPTION 'Invalid geometry type (%), expecting ''ST_MultiPolygon'' or ''ST_Polygon''', ST_GeometryType(geom);
|
|
END IF;
|
|
|
|
-- TODO: add timespan in search
|
|
-- TODO: add overlap info in search
|
|
-- TODO: turn this into a function _OBS_GetGeometryMetadata(geo_ref_name, tablename, and geom_colname)
|
|
-- SELECT geo_ref_name, tablename, geom_colname INTO a, b, c
|
|
-- FROM _OBS_GetGeometryMetadata
|
|
SELECT * INTO geoid_colname, target_table, geom_colname
|
|
FROM cdb_observatory._OBS_GetGeometryMetadata(boundary_id);
|
|
|
|
-- if no tables are found, raise notice and return null
|
|
IF target_table IS NULL
|
|
THEN
|
|
RAISE NOTICE 'No boundaries found for bounding box ''%'' in ''%''', ST_AsText(geom), boundary_id;
|
|
RETURN QUERY SELECT NULL::geometry, NULL::text;
|
|
END IF;
|
|
|
|
RAISE NOTICE 'target_table: %', target_table;
|
|
|
|
-- return first boundary in intersections
|
|
RETURN QUERY
|
|
EXECUTE format(
|
|
'SELECT t.%s, t.%s
|
|
FROM observatory.%s As t
|
|
WHERE ST_%s($1, t.the_geom)
|
|
', geom_colname, geoid_colname, target_table, overlap_type)
|
|
USING geom;
|
|
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- OBS_GetBoundariesByPointAndRadius
|
|
--
|
|
-- Given a point and radius, and it's geometry level (see
|
|
-- OBS_ListGeomColumns() for all available boundary ids), give back the
|
|
-- boundaries that are contained within the point buffered by radius meters and
|
|
-- the associated geometry ids
|
|
|
|
-- Inputs:
|
|
-- geom geometry: point geometry centered on area of interest
|
|
-- radius numeric: radius (in meters) of a circle centered on geom for
|
|
-- selecting polygons
|
|
-- boundary_id text: source id of boundaries (e.g., us.census.tiger.county)
|
|
-- see function OBS_ListGeomColumns for all avaiable
|
|
-- boundary ids
|
|
-- time_span text: time span that the geometries were collected (optional)
|
|
--
|
|
-- Output:
|
|
-- table with the following columns
|
|
-- boundary geometry: geometry boundary that is contained within the input
|
|
-- bounding box at the requested geometry level
|
|
-- with boundary_id, and time_span
|
|
-- geom_refs text: geometry identifiers (e.g., geoid for the US Census)
|
|
--
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetBoundariesByPointAndRadius(
|
|
geom geometry(Geometry, 4326), -- point
|
|
radius numeric, -- radius in meters
|
|
boundary_id text,
|
|
time_span text DEFAULT NULL,
|
|
overlap_type text DEFAULT 'intersects')
|
|
RETURNS TABLE(boundary geometry, geom_refs text)
|
|
AS $$
|
|
DECLARE
|
|
circle_boundary geometry(Geometry, 4326);
|
|
BEGIN
|
|
|
|
circle_boundary := ST_Buffer(geom::geography, radius)::geometry;
|
|
|
|
RETURN QUERY SELECT *
|
|
FROM cdb_observatory.OBS_GetBoundariesByBBox(
|
|
circle_boundary,
|
|
boundary_id,
|
|
time_span);
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- OBS_GetPointsByBBox
|
|
--
|
|
-- Given a bounding box (or a polygon), and it's geometry level (see
|
|
-- OBS_ListGeomColumns() for all available boundary ids), give back a point
|
|
-- which lies in a boundary from the requested geometry level that is contained
|
|
-- within the bounding box polygon and the associated geometry ids
|
|
--
|
|
-- Inputs:
|
|
-- geom geometry: bounding box (or polygon) of the region of interest
|
|
-- boundary_id text: source id of boundaries (e.g., us.census.tiger.county)
|
|
-- see function OBS_ListGeomColumns for all avaiable
|
|
-- boundary ids
|
|
-- time_span text: time span that the geometries were collected (optional)
|
|
--
|
|
-- Output:
|
|
-- table with the following columns
|
|
-- boundary geometry: point that lies on a boundary that is contained within
|
|
-- the input bounding box at the requested geometry
|
|
-- level with boundary_id, and time_span
|
|
-- geom_refs text: geometry identifiers (e.g., geoid for the US Census)
|
|
--
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetPointsByBBox(
|
|
geom geometry(Geometry, 4326),
|
|
boundary_id text,
|
|
time_span text DEFAULT NULL,
|
|
overlap_type text DEFAULT 'intersects')
|
|
RETURNS TABLE(boundary geometry, geom_refs text)
|
|
AS $$
|
|
DECLARE
|
|
boundary geometry(Geometry, 4326);
|
|
geom_colname text;
|
|
geoid_colname text;
|
|
target_table text;
|
|
BEGIN
|
|
|
|
IF lower(overlap_type) NOT IN ('contains', 'within', 'intersects')
|
|
THEN
|
|
RAISE EXCEPTION 'Overlap type ''%'' is not an accepted type (choose contains, within, or intersects)', overlap_type;
|
|
ELSIF ST_GeometryType(geom) NOT IN ('ST_Polygon', 'ST_MultiPolygon')
|
|
THEN
|
|
RAISE EXCEPTION 'Invalid geometry type (%), expecting ''ST_MultiPolygon'' or ''ST_Polygon''', ST_GeometryType(geom);
|
|
END IF;
|
|
|
|
SELECT * INTO geoid_colname, target_table, geom_colname
|
|
FROM cdb_observatory._OBS_GetGeometryMetadata(boundary_id);
|
|
|
|
-- if no tables are found, raise notice and return null
|
|
IF target_table IS NULL
|
|
THEN
|
|
RAISE NOTICE 'No boundaries found for bounding box ''%'' in ''%''', ST_AsText(geom), boundary_id;
|
|
RETURN QUERY SELECT NULL::geometry, NULL::text;
|
|
END IF;
|
|
|
|
RAISE NOTICE 'target_table: %', target_table;
|
|
|
|
-- return first boundary in intersections
|
|
RETURN QUERY
|
|
EXECUTE format(
|
|
'SELECT ST_PointOnSurface(t.%s) As %s, t.%s
|
|
FROM observatory.%s As t
|
|
WHERE ST_%s($1, t.the_geom)
|
|
', geom_colname, geom_colname, geoid_colname, target_table, overlap_type)
|
|
USING geom;
|
|
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- OBS_GetBoundariesByPointAndRadius
|
|
--
|
|
-- Given a point and radius, and it's geometry level (see
|
|
-- OBS_ListGeomColumns() for all available boundary ids), give back the
|
|
-- boundaries that are contained within the point buffered by radius meters and
|
|
-- the associated geometry ids
|
|
|
|
-- Inputs:
|
|
-- geom geometry: point geometry centered on area of interest
|
|
-- radius numeric: radius (in meters) of a circle centered on geom for
|
|
-- selecting polygons
|
|
-- boundary_id text: source id of boundaries (e.g., us.census.tiger.county)
|
|
-- see function OBS_ListGeomColumns for all avaiable
|
|
-- boundary ids
|
|
-- time_span text: time span that the geometries were collected (optional)
|
|
--
|
|
-- Output:
|
|
-- table with the following columns
|
|
-- boundary geometry: geometry boundary that is contained within the input
|
|
-- bounding box at the requested geometry level
|
|
-- with boundary_id, and time_span
|
|
-- geom_refs text: geometry identifiers (e.g., geoid for the US Census)
|
|
--
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetPointsByPointAndRadius(
|
|
geom geometry(Geometry, 4326), -- point
|
|
radius numeric, -- radius in meters
|
|
boundary_id text,
|
|
time_span text DEFAULT NULL,
|
|
overlap_type text DEFAULT 'intersects')
|
|
RETURNS TABLE(boundary geometry, geom_refs text)
|
|
AS $$
|
|
BEGIN
|
|
|
|
RETURN QUERY SELECT *
|
|
FROM cdb_observatory.OBS_GetPointsByBBox(
|
|
ST_Buffer(geom::geography, radius)::geometry,
|
|
boundary_id,
|
|
time_span,
|
|
overlap_type);
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
|
|
-- _OBS_GetGeometryMetadata()
|
|
-- TODO: add timespan in search
|
|
-- TODO: add choice of clipped versus not clipped
|
|
CREATE OR REPLACE FUNCTION cdb_observatory._OBS_GetGeometryMetadata(boundary_id text)
|
|
RETURNS table(geoid_colname text, target_table text, geom_colname text)
|
|
AS $$
|
|
BEGIN
|
|
|
|
RETURN QUERY
|
|
EXECUTE
|
|
format($string$
|
|
SELECT geoid_ct.colname As geoid_colname,
|
|
tablename,
|
|
geom_ct.colname As geom_colname
|
|
FROM observatory.obs_column_table As geoid_ct,
|
|
observatory.obs_table As geom_t,
|
|
observatory.obs_column_table As geom_ct,
|
|
observatory.obs_column As geom_c
|
|
WHERE geoid_ct.column_id
|
|
IN (
|
|
SELECT source_id
|
|
FROM observatory.obs_column_to_column
|
|
WHERE reltype = 'geom_ref'
|
|
AND target_id = '%s'
|
|
)
|
|
AND geoid_ct.table_id = geom_t.id and
|
|
geom_t.id = geom_ct.table_id and
|
|
geom_ct.column_id = geom_c.id and
|
|
geom_c.type ILIKE 'geometry'
|
|
-- TODO: filter by clipped vs. not so appropriate tablename are unique
|
|
-- so the limit 1 can be removed
|
|
LIMIT 1
|
|
$string$, boundary_id);
|
|
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|