1122 lines
50 KiB
PL/PgSQL
1122 lines
50 KiB
PL/PgSQL
--Functions for augmenting specific tables
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Creates a table of demographic snapshot
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetDemographicSnapshot(geom geometry(Geometry, 4326),
|
|
timespan text DEFAULT NULL,
|
|
boundary_id text DEFAULT NULL
|
|
) RETURNS SETOF JSON
|
|
AS $$
|
|
DECLARE
|
|
meta JSON;
|
|
BEGIN
|
|
boundary_id = COALESCE(boundary_id, 'us.census.tiger.census_tract');
|
|
|
|
EXECUTE $query$ SELECT cdb_observatory.OBS_GetMeta($1,
|
|
('[ ' ||
|
|
'{"numer_id": "us.census.acs.B01003001", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B01001002", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B01001026", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B01002001", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B03002003", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B03002004", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B03002006", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B03002012", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B03002005", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B03002008", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B03002009", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B03002002", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B11001001", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B15003001", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B15003017", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B15003019", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B15003020", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B15003021", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B15003022", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B15003023", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19013001", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19083001", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19301001", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25001001", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25002003", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25004002", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25004004", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25058001", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25071001", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25075001", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25075025", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25081002", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08134001", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08134002", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08134003", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08134004", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08134005", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08134006", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08134007", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08134008", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08134009", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08134010", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08135001", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001002", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001003", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001004", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001005", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001006", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001007", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001008", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001009", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001010", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001011", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001012", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001013", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001014", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001015", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001016", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19001017", "numer_timespan": ' || $2 || ', "geom_id": ' || $3 || '}' ||
|
|
']')::JSON)
|
|
$query$
|
|
INTO meta
|
|
USING geom,
|
|
COALESCE('"' || timespan || '"', 'null'),
|
|
COALESCE('"' || boundary_id || '"', 'null');
|
|
|
|
RETURN QUERY EXECUTE $query$
|
|
WITH vals AS (SELECT JSON_Array_Elements(data)->'value' val,
|
|
JSON_Array_Elements($2) meta
|
|
FROM cdb_observatory.OBS_GetData( ARRAY[($1, 1)::geomval], $2))
|
|
SELECT JSON_Build_Object(
|
|
'value', val,
|
|
'id', meta->'numer_id',
|
|
'name', meta->'numer_name',
|
|
'type', meta->'numer_type',
|
|
'description', meta->'numer_description'
|
|
) FROM vals
|
|
$query$
|
|
USING geom, meta
|
|
RETURN;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetMeta(
|
|
geom geometry(Geometry, 4326),
|
|
params JSON,
|
|
num_timespan_options INTEGER DEFAULT NULL, -- how many timespan options to show
|
|
num_score_options INTEGER DEFAULT NULL, -- how many score options to show
|
|
target_geoms INTEGER DEFAULT NULL
|
|
)
|
|
RETURNS JSON
|
|
AS $$
|
|
DECLARE
|
|
numer_filters TEXT[];
|
|
geom_filters TEXT[];
|
|
meta_filter_clause TEXT;
|
|
scores_clause TEXT;
|
|
result JSON;
|
|
BEGIN
|
|
IF num_timespan_options IS NULL THEN
|
|
num_timespan_options := 1;
|
|
END IF;
|
|
IF num_score_options IS NULL THEN
|
|
num_score_options := 1;
|
|
END IF;
|
|
|
|
numer_filters := (SELECT Array_Agg(val) FILTER (WHERE val IS NOT NULL) FROM (SELECT (JSON_Array_Elements(params))->>'numer_id' val) foo);
|
|
geom_filters := (SELECT Array_Agg(val) FILTER (WHERE val IS NOT NULL) FROM (SELECT (JSON_Array_Elements(params))->>'geom_id' val) bar);
|
|
meta_filter_clause := '(m.numer_id = ANY ($6) OR m.geom_id = ANY ($7))';
|
|
|
|
scores_clause := ' agg_geoms AS (
|
|
SELECT target_geoms, target_area, ARRAY_AGG(geom_id) geom_ids
|
|
FROM meta
|
|
GROUP BY target_geoms, target_area
|
|
), scores AS (
|
|
SELECT target_geoms, target_area,
|
|
CASE target_area
|
|
-- point-specific, just order by numgeoms instead of score
|
|
WHEN 0 THEN scores.numgeoms
|
|
-- has some area, use proper scoring
|
|
ELSE scores.score
|
|
END AS score,
|
|
scores.numgeoms, scores.table_id, scores.column_id
|
|
FROM agg_geoms,
|
|
LATERAL cdb_observatory._OBS_GetGeometryScores($1,
|
|
geom_ids, COALESCE(target_geoms, $2), target_area) scores
|
|
) ';
|
|
|
|
IF JSON_Array_Length(params) = 1 THEN
|
|
IF numer_filters IS NULL AND geom_filters IS NOT NULL THEN
|
|
meta_filter_clause := 'm.geom_id = ($7)[1]';
|
|
ELSIF geom_filters IS NULL AND numer_filters IS NOT NULL THEN
|
|
meta_filter_clause := 'm.numer_id = ($6)[1]';
|
|
ELSIF numer_filters IS NOT NULL AND geom_filters IS NOT NULL THEN
|
|
meta_filter_clause := 'm.numer_id = ($6)[1] AND m.geom_id = ($7)[1]';
|
|
ELSE
|
|
RAISE EXCEPTION 'Must pass either numer_id or geom_id to every key in GetMeta';
|
|
END IF;
|
|
|
|
IF geom_filters IS NOT NULL AND numer_filters IS NOT NULL THEN
|
|
scores_clause := 'scores AS (
|
|
SELECT NULL::INTEGER target_geoms, NULL::Numeric target_area,
|
|
1 score, null, geom_tid table_id, geom_id column_id,
|
|
NULL::Integer numgeoms
|
|
FROM meta) ';
|
|
END IF;
|
|
END IF;
|
|
|
|
EXECUTE format($string$
|
|
WITH _filters AS (SELECT
|
|
row_number() over () id, *
|
|
FROM json_to_recordset($3)
|
|
AS x(numer_id TEXT, denom_id TEXT, geom_id TEXT, numer_timespan TEXT,
|
|
geom_timespan TEXT, normalization TEXT, max_timespan_rank TEXT,
|
|
max_score_rank TEXT, target_geoms INTEGER, target_area Numeric
|
|
)
|
|
), meta AS (SELECT
|
|
id,
|
|
f.numer_id,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE numer_aggregate END numer_aggregate,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE numer_colname END numer_colname,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE numer_geomref_colname END numer_geomref_colname,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE numer_tablename END numer_tablename,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE numer_type END numer_type,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE numer_name END numer_name,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE numer_description END numer_description,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE numer_t_description END numer_t_description,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE m.numer_timespan END numer_timespan,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE m.denom_id END denom_id,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE denom_aggregate END denom_aggregate,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE denom_colname END denom_colname,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE denom_geomref_colname END denom_geomref_colname,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE denom_tablename END denom_tablename,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE denom_name END denom_name,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE denom_description END denom_description,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE denom_t_description END denom_t_description,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE denom_type END denom_type,
|
|
CASE WHEN f.numer_id IS NULL THEN NULL ELSE denom_reltype END denom_reltype,
|
|
m.geom_id,
|
|
m.geom_timespan,
|
|
geom_colname,
|
|
geom_tid,
|
|
geom_geomref_colname,
|
|
geom_tablename,
|
|
geom_name,
|
|
geom_description,
|
|
geom_t_description,
|
|
geom_type,
|
|
Coalesce(normalization,
|
|
-- automatically assign normalization to numeric numerators
|
|
CASE WHEN cdb_observatory.isnumeric(numer_type) THEN
|
|
CASE WHEN denom_reltype ILIKE 'denominator' THEN 'denominated'
|
|
WHEN numer_aggregate ILIKE 'sum' THEN 'area'
|
|
WHEN numer_aggregate IN ('median', 'average') AND denom_reltype ILIKE 'universe'
|
|
THEN 'prenormalized'
|
|
ELSE 'prenormalized'
|
|
END ELSE NULL
|
|
END
|
|
) normalization,
|
|
max_timespan_rank,
|
|
max_score_rank,
|
|
target_geoms,
|
|
target_area
|
|
FROM observatory.obs_meta m JOIN _filters f
|
|
ON CASE WHEN f.numer_id IS NULL THEN m.geom_id ELSE m.numer_id END =
|
|
CASE WHEN f.numer_id IS NULL THEN f.geom_id ELSE f.numer_id END
|
|
WHERE
|
|
%s
|
|
AND (m.numer_id = f.numer_id OR COALESCE(f.numer_id, '') = '')
|
|
AND (m.denom_id = f.denom_id OR COALESCE(f.denom_id, '') = '')
|
|
AND (m.geom_id = f.geom_id OR COALESCE(f.geom_id, '') = '')
|
|
AND (m.geom_timespan = f.geom_timespan OR COALESCE(f.geom_timespan, '') = '')
|
|
AND (m.numer_timespan = f.numer_timespan OR COALESCE(f.numer_timespan, '') = '')
|
|
), %s
|
|
, groups AS (SELECT
|
|
id,
|
|
scores.score,
|
|
numer_timespan,
|
|
dense_rank() OVER (PARTITION BY id ORDER BY numer_timespan DESC) timespan_rank,
|
|
dense_rank() OVER (PARTITION BY id ORDER BY score DESC) score_rank,
|
|
json_build_object(
|
|
'id', id,
|
|
'numer_id', numer_id,
|
|
'timespan_rank', dense_rank() OVER (PARTITION BY id ORDER BY numer_timespan DESC),
|
|
'score_rank', dense_rank() OVER (PARTITION BY id ORDER BY score DESC),
|
|
'timespan_rownum', row_number() over
|
|
(PARTITION BY id, score ORDER BY numer_timespan DESC, Coalesce(denom_id, '')),
|
|
'score_rownum', row_number() over
|
|
(PARTITION BY id, numer_timespan ORDER BY score DESC, Coalesce(denom_id, '')),
|
|
'score', scores.score,
|
|
'suggested_name', cdb_observatory.FIRST(
|
|
LOWER(TRIM(BOTH '_' FROM regexp_replace(CASE WHEN numer_id IS NOT NULL
|
|
THEN CASE
|
|
WHEN normalization ILIKE 'area%%' THEN numer_colname || ' per sq km' || ' ' || numer_timespan
|
|
WHEN normalization ILIKE 'denom%%' THEN numer_colname || ' ' || numer_timespan || ' by ' || denom_colname
|
|
ELSE numer_colname || ' ' || numer_timespan
|
|
END
|
|
ELSE geom_name || ' ' || geom_timespan
|
|
END, '[^a-zA-Z0-9]+', '_', 'g')))
|
|
),
|
|
'numer_aggregate', cdb_observatory.FIRST(meta.numer_aggregate),
|
|
'numer_colname', cdb_observatory.FIRST(meta.numer_colname),
|
|
'numer_geomref_colname', cdb_observatory.FIRST(meta.numer_geomref_colname),
|
|
'numer_tablename', cdb_observatory.FIRST(meta.numer_tablename),
|
|
'numer_type', cdb_observatory.FIRST(meta.numer_type),
|
|
'numer_description', cdb_observatory.FIRST(meta.numer_description),
|
|
'numer_t_description', cdb_observatory.FIRST(meta.numer_t_description),
|
|
'denom_aggregate', cdb_observatory.FIRST(meta.denom_aggregate),
|
|
'denom_colname', cdb_observatory.FIRST(denom_colname),
|
|
'denom_geomref_colname', cdb_observatory.FIRST(denom_geomref_colname),
|
|
'denom_tablename', cdb_observatory.FIRST(denom_tablename),
|
|
'denom_type', cdb_observatory.FIRST(meta.denom_type),
|
|
'denom_reltype', cdb_observatory.FIRST(meta.denom_reltype),
|
|
'denom_description', cdb_observatory.FIRST(meta.denom_description),
|
|
'denom_t_description', cdb_observatory.FIRST(meta.denom_t_description),
|
|
'geom_colname', cdb_observatory.FIRST(geom_colname),
|
|
'geom_geomref_colname', cdb_observatory.FIRST(geom_geomref_colname),
|
|
'geom_tablename', cdb_observatory.FIRST(geom_tablename),
|
|
'geom_type', cdb_observatory.FIRST(meta.geom_type),
|
|
'geom_timespan', cdb_observatory.FIRST(meta.geom_timespan),
|
|
'geom_description', cdb_observatory.FIRST(meta.geom_description),
|
|
'geom_t_description', cdb_observatory.FIRST(meta.geom_t_description),
|
|
'numer_timespan', cdb_observatory.FIRST(numer_timespan),
|
|
'numer_name', cdb_observatory.FIRST(numer_name),
|
|
'denom_name', cdb_observatory.FIRST(denom_name),
|
|
'geom_name', cdb_observatory.FIRST(geom_name),
|
|
'normalization', cdb_observatory.FIRST(normalization),
|
|
'max_timespan_rank', cdb_observatory.FIRST(max_timespan_rank),
|
|
'max_score_rank', cdb_observatory.FIRST(max_score_rank),
|
|
'target_geoms', cdb_observatory.FIRST(scores.target_geoms),
|
|
'target_area', cdb_observatory.FIRST(scores.target_area),
|
|
'num_geoms', cdb_observatory.FIRST(scores.numgeoms),
|
|
'denom_id', denom_id,
|
|
'geom_id', meta.geom_id
|
|
) metadata
|
|
FROM meta, scores
|
|
WHERE meta.geom_id = scores.column_id
|
|
AND meta.geom_tid = scores.table_id
|
|
AND COALESCE(meta.target_geoms, 0) = COALESCE(scores.target_geoms, 0)
|
|
AND COALESCE(meta.target_area, 0) = COALESCE(scores.target_area, 0)
|
|
GROUP BY id, score, numer_id, denom_id, geom_id, numer_timespan
|
|
) SELECT JSON_AGG(metadata ORDER BY id)
|
|
FROM groups
|
|
WHERE timespan_rank <= Coalesce((metadata->>'max_timespan_rank')::INTEGER, 'infinity'::FLOAT)
|
|
AND score_rank <= Coalesce((metadata->>'max_score_rank')::INTEGER, 1)
|
|
AND (metadata->>'timespan_rownum')::INTEGER <= $4
|
|
AND (metadata->>'score_rownum')::INTEGER <= $5
|
|
$string$, meta_filter_clause, scores_clause)
|
|
INTO result
|
|
USING
|
|
CASE WHEN ST_GeometryType(geom) = 'ST_Point' THEN
|
|
ST_Buffer(geom::geography, 200)::geometry(geometry, 4326)
|
|
ELSE geom
|
|
END,
|
|
target_geoms,
|
|
params,
|
|
num_timespan_options,
|
|
num_score_options, numer_filters, geom_filters
|
|
;
|
|
RETURN result;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetMeasure(
|
|
geom geometry(Geometry, 4326),
|
|
measure_id TEXT,
|
|
normalize TEXT DEFAULT NULL,
|
|
boundary_id TEXT DEFAULT NULL,
|
|
time_span TEXT DEFAULT NULL,
|
|
simplification NUMERIC DEFAULT 0.00001
|
|
)
|
|
RETURNS NUMERIC
|
|
AS $$
|
|
DECLARE
|
|
geom_type TEXT;
|
|
params JSON;
|
|
map_type TEXT;
|
|
result Numeric;
|
|
numer_aggregate TEXT;
|
|
BEGIN
|
|
IF geom IS NULL THEN
|
|
RETURN NULL;
|
|
END IF;
|
|
|
|
IF simplification IS NOT NULL THEN
|
|
geom := ST_Simplify(geom, simplification);
|
|
END IF;
|
|
|
|
IF ST_GeometryType(geom) = 'ST_Point' THEN
|
|
geom_type := 'point';
|
|
ELSIF ST_GeometryType(geom) IN ('ST_Polygon', 'ST_MultiPolygon') THEN
|
|
geom_type := 'polygon';
|
|
geom := ST_CollectionExtract(ST_MakeValid(geom), 3);
|
|
ELSE
|
|
RAISE EXCEPTION 'Invalid geometry type (%), can only handle ''ST_Point'', ''ST_Polygon'', and ''ST_MultiPolygon''',
|
|
ST_GeometryType(geom);
|
|
END IF;
|
|
|
|
params := (SELECT cdb_observatory.OBS_GetMeta(
|
|
geom, JSON_Build_Array(JSON_Build_Object('numer_id', measure_id,
|
|
'geom_id', boundary_id,
|
|
'numer_timespan', time_span
|
|
)), 1, 1, 500));
|
|
numer_aggregate := params->0->>'numer_aggregate';
|
|
|
|
IF normalize ILIKE 'area%' AND numer_aggregate ILIKE 'sum' THEN
|
|
map_type := 'areaNormalized';
|
|
ELSIF normalize ILIKE 'denom%' THEN
|
|
map_type := 'denominated';
|
|
ELSIF normalize ILIKE 'pre%' THEN
|
|
map_type := 'predenominated';
|
|
ELSE
|
|
-- defaults: area normalization for point if it's possible and none for
|
|
-- polygon or non-summable point
|
|
IF geom_type = 'point' AND numer_aggregate ILIKE 'sum' THEN
|
|
map_type := 'areaNormalized';
|
|
ELSE
|
|
map_type := 'predenominated';
|
|
END IF;
|
|
END IF;
|
|
|
|
params := JSON_Build_Array(JSONB_Set((params::JSONB)->0, '{normalization}', to_jsonb(map_type))::JSON);
|
|
|
|
IF params->0->>'geom_id' IS NULL THEN
|
|
RAISE NOTICE 'No boundary found for geom';
|
|
RETURN NULL;
|
|
ELSE
|
|
RAISE NOTICE 'Using boundary %', params->0->>'geom_id';
|
|
END IF;
|
|
|
|
EXECUTE $query$
|
|
SELECT (data->0->>'value')::Numeric FROM
|
|
cdb_observatory.OBS_GetData(ARRAY[($1, 1)::geomval], $2)
|
|
$query$
|
|
INTO result
|
|
USING geom, params;
|
|
|
|
RETURN result;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetMeasureById(
|
|
geom_ref TEXT,
|
|
measure_id TEXT,
|
|
boundary_id TEXT,
|
|
time_span TEXT DEFAULT NULL
|
|
)
|
|
RETURNS NUMERIC
|
|
AS $$
|
|
DECLARE
|
|
result NUMERIC;
|
|
BEGIN
|
|
IF geom_ref IS NULL THEN
|
|
RETURN NULL;
|
|
ELSIF boundary_id IS NULL THEN
|
|
RETURN NULL;
|
|
END IF;
|
|
|
|
EXECUTE $query$
|
|
SELECT data->0->>'value'
|
|
FROM cdb_observatory.OBS_GetData(Array[$1],
|
|
cdb_observatory.OBS_GetMeta(ST_MakeEnvelope(-180, -90, 180, 90, 4326),
|
|
JSON_Build_Array(JSON_Build_Object(
|
|
'numer_id', $2,
|
|
'geom_id', $3,
|
|
'numer_timespan', $4,
|
|
'normalization', 'predenominated'
|
|
))))
|
|
$query$
|
|
INTO result
|
|
USING geom_ref, measure_id, boundary_id, time_span;
|
|
|
|
RETURN result;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
|
|
-- GetData that obtains data from array of geomrefs
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetData(
|
|
geomrefs text[],
|
|
params JSON
|
|
)
|
|
RETURNS TABLE (
|
|
id TEXT,
|
|
data JSON
|
|
)
|
|
AS $$
|
|
DECLARE
|
|
colspecs TEXT;
|
|
tables TEXT;
|
|
obs_wheres TEXT;
|
|
user_wheres TEXT;
|
|
|
|
q text;
|
|
BEGIN
|
|
IF params IS NULL OR JSON_ARRAY_LENGTH(params) = 0 THEN
|
|
RETURN QUERY EXECUTE $query$ SELECT NULL::TEXT, NULL::JSON LIMIT 0 $query$;
|
|
RETURN;
|
|
END IF;
|
|
|
|
EXECUTE
|
|
$query$
|
|
WITH _meta AS (SELECT
|
|
generate_series(1, array_length($1, 1)) colid,
|
|
(unnest($1))->>'id' id,
|
|
(unnest($1))->>'numer_id' numer_id,
|
|
(unnest($1))->>'numer_aggregate' numer_aggregate,
|
|
(unnest($1))->>'numer_colname' numer_colname,
|
|
(unnest($1))->>'numer_geomref_colname' numer_geomref_colname,
|
|
(unnest($1))->>'numer_tablename' numer_tablename,
|
|
(unnest($1))->>'numer_type' numer_type,
|
|
(unnest($1))->>'denom_id' denom_id,
|
|
(unnest($1))->>'denom_aggregate' denom_aggregate,
|
|
(unnest($1))->>'denom_colname' denom_colname,
|
|
(unnest($1))->>'denom_geomref_colname' denom_geomref_colname,
|
|
(unnest($1))->>'denom_tablename' denom_tablename,
|
|
(unnest($1))->>'denom_type' denom_type,
|
|
(unnest($1))->>'denom_reltype' denom_reltype,
|
|
(unnest($1))->>'geom_id' geom_id,
|
|
(unnest($1))->>'geom_colname' geom_colname,
|
|
(unnest($1))->>'geom_geomref_colname' geom_geomref_colname,
|
|
(unnest($1))->>'geom_tablename' geom_tablename,
|
|
(unnest($1))->>'geom_type' geom_type,
|
|
(unnest($1))->>'geom_timespan' geom_timespan,
|
|
(unnest($1))->>'numer_timespan' numer_timespan,
|
|
(unnest($1))->>'normalization' normalization,
|
|
(unnest($1))->>'api_method' api_method,
|
|
(unnest($1))->'api_args' api_args
|
|
)
|
|
SELECT String_Agg(
|
|
-- numeric
|
|
'JSON_Build_Object(' || CASE
|
|
WHEN api_method IS NOT NULL THEN
|
|
'''value'', ' ||
|
|
'ARRAY_AGG( ' ||
|
|
api_method || '.' || numer_colname || ')::' || numer_type || '[]'
|
|
-- numeric internal values
|
|
WHEN cdb_observatory.isnumeric(numer_type) THEN
|
|
'''value'', ' || CASE
|
|
-- denominated
|
|
WHEN LOWER(normalization) LIKE 'denom%' OR (normalization IS NULL AND denom_id IS NOT NULL)
|
|
THEN 'cdb_observatory.FIRST(' || numer_tablename || '.' || numer_colname ||
|
|
' / NullIf(' || denom_tablename || '.' || denom_colname || ', 0))'
|
|
-- areaNormalized
|
|
WHEN LOWER(normalization) LIKE 'area%' OR (normalization IS NULL AND numer_aggregate ILIKE 'sum')
|
|
THEN 'cdb_observatory.FIRST(' || numer_tablename || '.' || numer_colname ||
|
|
' / (ST_Area(' || geom_tablename || '.' || geom_colname || '::Geography)/1000000))'
|
|
-- prenormalized
|
|
ELSE 'cdb_observatory.FIRST(' || numer_tablename || '.' || numer_colname || ')'
|
|
END || ':: ' || numer_type
|
|
|
|
-- categorical/text
|
|
WHEN LOWER(numer_type) LIKE 'text' THEN
|
|
'''value'', ' || 'cdb_observatory.FIRST(' || numer_tablename || '.' || numer_colname || ') '
|
|
|
|
-- geometry
|
|
WHEN numer_id IS NULL THEN
|
|
'''geomref'', ' || 'cdb_observatory.FIRST(' || geom_tablename ||
|
|
'.' || geom_geomref_colname || '), ' ||
|
|
'''value'', ' || 'cdb_observatory.FIRST(' || geom_tablename ||
|
|
'.' || geom_colname || ')'
|
|
ELSE ''
|
|
END || ')', ', ')
|
|
AS colspecs,
|
|
|
|
(SELECT String_Agg(DISTINCT CASE
|
|
-- External API
|
|
WHEN tablename LIKE 'cdb_observatory.%' THEN
|
|
'LATERAL (SELECT * FROM ' || tablename || ') ' ||
|
|
REPLACE(split_part(tablename, '(', 1), 'cdb_observatory.', '')
|
|
-- Internal obs_ table
|
|
ELSE 'observatory.' || tablename
|
|
END, ', ') FROM (
|
|
SELECT DISTINCT UNNEST(tablenames_ary) tablename FROM (
|
|
SELECT ARRAY_AGG(numer_tablename) ||
|
|
ARRAY_AGG(denom_tablename) ||
|
|
ARRAY_AGG(geom_tablename) ||
|
|
ARRAY_AGG('cdb_observatory.' || api_method || '(_geomrefs.id' || COALESCE(', ' ||
|
|
(SELECT STRING_AGG(REPLACE(val::text, '"', ''''), ', ')
|
|
FROM (SELECT json_array_elements(api_args) as val) as vals),
|
|
'') || ')')
|
|
tablenames_ary
|
|
) tablenames_inner
|
|
) tablenames_outer) tablenames,
|
|
|
|
String_Agg(DISTINCT array_to_string(ARRAY[
|
|
CASE WHEN numer_tablename != geom_tablename
|
|
THEN numer_tablename || '.' || numer_geomref_colname || ' = ' ||
|
|
geom_tablename || '.' || geom_geomref_colname
|
|
ELSE NULL END,
|
|
CASE WHEN numer_tablename != denom_tablename
|
|
THEN numer_tablename || '.' || numer_geomref_colname || ' = ' ||
|
|
denom_tablename || '.' || denom_geomref_colname
|
|
ELSE NULL END
|
|
], ' AND '),
|
|
' AND ') AS obs_wheres,
|
|
|
|
String_Agg(geom_tablename || '.' || geom_geomref_colname || ' = ' ||
|
|
'_geomrefs.id', ' AND ')
|
|
AS user_wheres
|
|
FROM _meta
|
|
;
|
|
$query$
|
|
INTO colspecs, tables, obs_wheres, user_wheres
|
|
USING (SELECT ARRAY(SELECT json_array_elements_text(params))::json[]);
|
|
|
|
RETURN QUERY EXECUTE format($query$
|
|
WITH _geomrefs AS (SELECT UNNEST($1) as id)
|
|
SELECT _geomrefs.id, Array_to_JSON(ARRAY[%s]::JSON[])
|
|
FROM _geomrefs, %s
|
|
%s
|
|
GROUP BY _geomrefs.id
|
|
ORDER BY _geomrefs.id
|
|
$query$, colspecs, tables,
|
|
'WHERE ' || NULLIF(ARRAY_TO_STRING(ARRAY[
|
|
Nullif(obs_wheres, ''), Nullif(user_wheres, '')
|
|
], ' AND '), '')
|
|
)
|
|
USING geomrefs;
|
|
RETURN;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
|
|
-- GetData that obtains data from array of (geom, id) geomvals.
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetData(
|
|
geomvals geomval[],
|
|
params JSON,
|
|
merge BOOLEAN DEFAULT True
|
|
)
|
|
RETURNS TABLE (
|
|
id INT,
|
|
data JSON
|
|
)
|
|
AS $$
|
|
DECLARE
|
|
procgeom_clauses TEXT;
|
|
val_clauses TEXT;
|
|
json_clause TEXT;
|
|
geomtype TEXT;
|
|
BEGIN
|
|
IF params IS NULL OR JSON_ARRAY_LENGTH(params) = 0 OR ARRAY_LENGTH(geomvals, 1) IS NULL THEN
|
|
RETURN QUERY EXECUTE $query$ SELECT NULL::INT, NULL::JSON LIMIT 0 $query$;
|
|
RETURN;
|
|
END IF;
|
|
|
|
geomtype := ST_GeometryType(geomvals[1].geom);
|
|
|
|
/* Read metadata to generate clauses for query */
|
|
EXECUTE $query$
|
|
WITH _meta AS (SELECT
|
|
row_number() over () colid, *
|
|
FROM json_to_recordset($1)
|
|
AS x(id TEXT, numer_id TEXT, numer_aggregate TEXT, numer_colname TEXT,
|
|
numer_geomref_colname TEXT, numer_tablename TEXT, numer_type TEXT,
|
|
denom_id TEXT, denom_aggregate TEXT, denom_colname TEXT,
|
|
denom_geomref_colname TEXT, denom_tablename TEXT, denom_type TEXT,
|
|
denom_reltype TEXT, geom_id TEXT, geom_colname TEXT,
|
|
geom_geomref_colname TEXT, geom_tablename TEXT, geom_type TEXT,
|
|
numer_timespan TEXT, geom_timespan TEXT, normalization TEXT,
|
|
api_method TEXT, api_args JSON)
|
|
),
|
|
|
|
-- Generate procgeom clauses.
|
|
-- These join the users' geoms to the relevant geometries for the
|
|
-- asked-for measures in the Observatory.
|
|
_procgeom_clauses AS (
|
|
SELECT
|
|
'_procgeoms_' || Coalesce(geom_tablename || '_' || geom_geomref_colname, api_method) || ' AS (' ||
|
|
CASE WHEN api_method IS NULL THEN
|
|
'SELECT _geoms.id, ' ||
|
|
CASE $3 WHEN True THEN '_geoms.geom'
|
|
ELSE geom_tablename || '.' || geom_colname
|
|
END || ' AS geom, ' ||
|
|
geom_tablename || '.' || geom_geomref_colname || ' AS geomref, ' ||
|
|
CASE
|
|
WHEN $2 = 'ST_Point' THEN
|
|
' Nullif(ST_Area(' || geom_tablename || '.' || geom_colname || '::Geography), 0)/1000000 ' ||
|
|
' AS area'
|
|
-- for numeric areas, include more complex calcs
|
|
ELSE
|
|
'CASE WHEN ST_Within(_geoms.geom, ' || geom_tablename || '.' || geom_colname || ')
|
|
THEN ST_Area(_geoms.geom) / Nullif(ST_Area(' || geom_tablename || '.' || geom_colname || '), 0)
|
|
WHEN ST_Within(' || geom_tablename || '.' || geom_colname || ', _geoms.geom)
|
|
THEN 1
|
|
ELSE ST_Area(cdb_observatory.safe_intersection(_geoms.geom, ' || geom_tablename || '.' || geom_colname || ')) /
|
|
Nullif(ST_Area(' || geom_tablename || '.' || geom_colname || '), 0)
|
|
END pct_obs'
|
|
END || '
|
|
FROM _geoms, observatory.' || geom_tablename || '
|
|
WHERE ST_Intersects(_geoms.geom, ' || geom_tablename || '.' || geom_colname || ')'
|
|
-- pass through input geometries for api_method
|
|
ELSE 'SELECT _geoms.id, _geoms.geom FROM _geoms'
|
|
END ||
|
|
') '
|
|
AS procgeom_clause
|
|
FROM _meta
|
|
GROUP BY api_method, geom_tablename, geom_geomref_colname, geom_colname
|
|
),
|
|
|
|
-- Generate val clauses.
|
|
-- These perform interpolations or other necessary calculations to
|
|
-- provide values according to users geometries.
|
|
_val_clauses AS (
|
|
SELECT
|
|
'_vals_' || Coalesce(geom_tablename || '_' || geom_geomref_colname, api_method) || ' AS (
|
|
SELECT _procgeoms.id, ' ||
|
|
String_Agg('json_build_object(' || CASE
|
|
-- api-delivered values
|
|
WHEN api_method IS NOT NULL THEN
|
|
'''value'', ' ||
|
|
'ARRAY_AGG( ' ||
|
|
api_method || '.' || numer_colname || ')::' || numer_type || '[]'
|
|
-- numeric internal values
|
|
WHEN cdb_observatory.isnumeric(numer_type) THEN
|
|
'''value'', ' || CASE
|
|
-- denominated
|
|
WHEN LOWER(normalization) LIKE 'denom%'
|
|
THEN CASE
|
|
WHEN denom_tablename IS NULL THEN ' NULL '
|
|
-- denominated point-in-poly
|
|
WHEN $2 = 'ST_Point' THEN
|
|
' cdb_observatory.FIRST(' || numer_tablename || '.' || numer_colname ||
|
|
' / NullIf(' || denom_tablename || '.' || denom_colname || ', 0))'
|
|
-- denominated polygon interpolation
|
|
-- SUM (numer * (% OBS geom in user geom)) / SUM (denom * (% OBS geom in user geom))
|
|
ELSE
|
|
' SUM(' || numer_tablename || '.' || numer_colname || ' ' ||
|
|
' * _procgeoms.pct_obs ' ||
|
|
' ) / NULLIF(SUM(' || denom_tablename || '.' || denom_colname || ' ' ||
|
|
' * _procgeoms.pct_obs), 0) '
|
|
END
|
|
-- areaNormalized
|
|
WHEN LOWER(normalization) LIKE 'area%'
|
|
THEN CASE
|
|
-- areaNormalized point-in-poly
|
|
WHEN $2 = 'ST_Point' THEN
|
|
' cdb_observatory.FIRST(' || numer_tablename || '.' || numer_colname ||
|
|
' / _procgeoms.area)'
|
|
-- areaNormalized polygon interpolation
|
|
-- SUM (numer * (% OBS geom in user geom)) / area of big geom
|
|
ELSE
|
|
--' NULL END '
|
|
' SUM(' || numer_tablename || '.' || numer_colname || ' ' ||
|
|
' * _procgeoms.pct_obs' ||
|
|
' ) / (Nullif(ST_Area(cdb_observatory.FIRST(_procgeoms.geom)::Geography), 0) / 1000000) '
|
|
END
|
|
-- median/average measures with universe
|
|
WHEN LOWER(numer_aggregate) IN ('median', 'average') AND
|
|
denom_reltype ILIKE 'universe' AND LOWER(normalization) LIKE 'pre%'
|
|
THEN CASE
|
|
-- predenominated point-in-poly
|
|
WHEN $2 = 'ST_Point' THEN
|
|
' cdb_observatory.FIRST(' || numer_tablename || '.' || numer_colname || ') '
|
|
ELSE
|
|
-- predenominated polygon interpolation weighted by universe
|
|
-- SUM (numer * denom * (% user geom in OBS geom)) / SUM (denom * (% user geom in OBS geom))
|
|
-- (10 * 1000 * 1) / (1000 * 1) = 10
|
|
-- (10 * 1000 * 1 + 50 * 10 * 1) / (1000 + 10) = 10500 / 10000 = 10.5
|
|
' SUM(' || numer_tablename || '.' || numer_colname ||
|
|
' * ' || denom_tablename || '.' || denom_colname ||
|
|
' * _procgeoms.pct_obs ' ||
|
|
' ) / Nullif(SUM(' || denom_tablename || '.' || denom_colname ||
|
|
' * _procgeoms.pct_obs ' || '), 0) '
|
|
END
|
|
-- prenormalized for summable measures. point or summable only!
|
|
WHEN numer_aggregate ILIKE 'sum' AND LOWER(normalization) LIKE 'pre%'
|
|
THEN CASE
|
|
-- predenominated point-in-poly
|
|
WHEN $2 = 'ST_Point' THEN
|
|
' cdb_observatory.FIRST(' || numer_tablename || '.' || numer_colname || ') '
|
|
ELSE
|
|
-- predenominated polygon interpolation
|
|
-- SUM (numer * (% user geom in OBS geom))
|
|
' SUM(' || numer_tablename || '.' || numer_colname || ' ' ||
|
|
' * _procgeoms.pct_obs) '
|
|
END
|
|
-- Everything else. Point only!
|
|
ELSE CASE
|
|
WHEN $2 = 'ST_Point' THEN
|
|
' cdb_observatory.FIRST(' || numer_tablename || '.' || numer_colname || ') '
|
|
ELSE
|
|
' cdb_observatory._OBS_RaiseNotice(''Cannot perform calculation over polygon for ' ||
|
|
numer_id || '/' || coalesce(denom_id, '') || '/' || geom_id || '/' || numer_timespan || ''')::Numeric '
|
|
END
|
|
END || '::' || numer_type
|
|
|
|
-- categorical/text
|
|
WHEN LOWER(numer_type) LIKE 'text' THEN
|
|
'''value'', ' || 'MODE() WITHIN GROUP (ORDER BY ' || numer_tablename || '.' || numer_colname || ') '
|
|
-- geometry
|
|
WHEN numer_id IS NULL THEN
|
|
'''geomref'', _procgeoms.geomref, ' ||
|
|
'''value'', ' || 'cdb_observatory.FIRST(_procgeoms.geom)::TEXT'
|
|
-- code below will return the intersection of the user's geom and the
|
|
-- OBS geom
|
|
--'''value'', ' || 'ST_Union(cdb_observatory.safe_intersection(_geoms.geom, ' || geom_tablename ||
|
|
-- '.' || geom_colname || '))::TEXT'
|
|
ELSE ''
|
|
END
|
|
|| ') val_' || colid, ', ')
|
|
|| '
|
|
FROM _procgeoms_' || Coalesce(geom_tablename || '_' || geom_geomref_colname, api_method) || ' _procgeoms ' ||
|
|
Coalesce(String_Agg(DISTINCT
|
|
Coalesce('LEFT JOIN observatory.' || numer_tablename || ' ON _procgeoms.geomref = observatory.' || numer_tablename || '.' || numer_geomref_colname,
|
|
', LATERAL (SELECT * FROM cdb_observatory.' || api_method || '(_procgeoms.geom' || Coalesce(', ' ||
|
|
(SELECT STRING_AGG(REPLACE(val::text, '"', ''''), ', ')
|
|
FROM (SELECT JSON_Array_Elements(api_args) as val) as vals),
|
|
'') || ')) AS ' || api_method)
|
|
, ' '), '') ||
|
|
CASE $3 WHEN True THEN E'\n GROUP BY _procgeoms.id ORDER BY _procgeoms.id '
|
|
ELSE E'\n GROUP BY _procgeoms.id, _procgeoms.geomref
|
|
ORDER BY _procgeoms.id, _procgeoms.geomref' END
|
|
|| ')'
|
|
AS val_clause,
|
|
'_vals_' || Coalesce(geom_tablename || '_' || geom_geomref_colname, api_method) AS cte_name
|
|
FROM _meta
|
|
GROUP BY geom_tablename, geom_geomref_colname, geom_colname, api_method
|
|
),
|
|
|
|
-- Generate clauses necessary to join together val_clauses
|
|
_val_joins AS (
|
|
SELECT String_Agg(a.cte_name || '.id = ' || b.cte_name || '.id ', ' AND ') val_joins
|
|
FROM _val_clauses a, _val_clauses b
|
|
WHERE a.cte_name != b.cte_name
|
|
AND a.cte_name < b.cte_name
|
|
),
|
|
|
|
-- Generate JSON clause. This puts together vals from val_clauses
|
|
_json_clause AS (SELECT
|
|
'SELECT ' || cdb_observatory.FIRST(cte_name) || '.id::INT,
|
|
Array_to_JSON(ARRAY[' || (SELECT String_Agg('val_' || colid, ', ') FROM _meta) || '])
|
|
FROM ' || String_Agg(cte_name, ', ') ||
|
|
Coalesce(' WHERE ' || val_joins, '')
|
|
AS json_clause
|
|
FROM _val_clauses, _val_joins
|
|
GROUP BY val_joins
|
|
)
|
|
|
|
SELECT (SELECT String_Agg(procgeom_clause, E',\n ') FROM _procgeom_clauses),
|
|
(SELECT String_Agg(val_clause, E',\n ') FROM _val_clauses),
|
|
json_clause
|
|
FROM _json_clause
|
|
$query$ INTO
|
|
procgeom_clauses,
|
|
val_clauses,
|
|
json_clause
|
|
USING params, geomtype, merge;
|
|
|
|
/* Execute query */
|
|
RETURN QUERY EXECUTE format($query$
|
|
WITH _raw_geoms AS (%s),
|
|
_geoms AS (SELECT id,
|
|
CASE WHEN (ST_NPoints(geom) > 1000)
|
|
THEN ST_CollectionExtract(ST_MakeValid(ST_SimplifyVW(geom, 0.00001)), 3)
|
|
ELSE geom END geom
|
|
FROM _raw_geoms),
|
|
-- procgeom_clauses
|
|
%s,
|
|
|
|
-- val_clauses
|
|
%s
|
|
|
|
-- json_clause
|
|
%s
|
|
$query$, CASE WHEN ARRAY_LENGTH(geomvals, 1) = 1
|
|
THEN ' SELECT $1[1].val as id, $1[1].geom as geom '
|
|
ELSE ' SELECT val as id, geom FROM UNNEST($1) '
|
|
END,
|
|
String_Agg(procgeom_clauses, E',\n '),
|
|
String_Agg(val_clauses, E',\n '),
|
|
json_clause)
|
|
USING geomvals;
|
|
RETURN;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetCategory(
|
|
geom geometry(Geometry, 4326),
|
|
category_id TEXT,
|
|
boundary_id TEXT DEFAULT NULL,
|
|
time_span TEXT DEFAULT NULL,
|
|
simplification NUMERIC DEFAULT 0.00001
|
|
)
|
|
RETURNS TEXT
|
|
AS $$
|
|
DECLARE
|
|
geom_type TEXT;
|
|
params JSON;
|
|
map_type TEXT;
|
|
result TEXT;
|
|
BEGIN
|
|
IF geom IS NULL THEN
|
|
RETURN NULL;
|
|
END IF;
|
|
|
|
IF simplification IS NOT NULL THEN
|
|
geom := ST_Simplify(geom, simplification);
|
|
END IF;
|
|
|
|
IF ST_GeometryType(geom) = 'ST_Point' THEN
|
|
geom_type := 'point';
|
|
ELSIF ST_GeometryType(geom) IN ('ST_Polygon', 'ST_MultiPolygon') THEN
|
|
geom_type := 'polygon';
|
|
geom := ST_CollectionExtract(ST_MakeValid(geom), 3);
|
|
ELSE
|
|
RAISE EXCEPTION 'Invalid geometry type (%), can only handle ''ST_Point'', ''ST_Polygon'', and ''ST_MultiPolygon''',
|
|
ST_GeometryType(geom);
|
|
END IF;
|
|
|
|
params := (SELECT cdb_observatory.OBS_GetMeta(
|
|
geom, JSON_Build_Array(JSON_Build_Object('numer_id', category_id,
|
|
'geom_id', boundary_id,
|
|
'numer_timespan', time_span
|
|
)), 1, 1, 500));
|
|
|
|
IF params->0->>'geom_id' IS NULL THEN
|
|
RAISE NOTICE 'No boundary found for geom';
|
|
RETURN NULL;
|
|
ELSE
|
|
RAISE NOTICE 'Using boundary %', params->0->>'geom_id';
|
|
END IF;
|
|
|
|
EXECUTE $query$
|
|
SELECT data->0->>'value' FROM
|
|
cdb_observatory.OBS_GetData(ARRAY[($1, 1)::geomval], $2)
|
|
$query$
|
|
INTO result
|
|
USING geom, params;
|
|
|
|
RETURN result;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetUSCensusMeasure(
|
|
geom geometry(Geometry, 4326),
|
|
name TEXT,
|
|
normalize TEXT DEFAULT NULL,
|
|
boundary_id TEXT DEFAULT NULL,
|
|
time_span TEXT DEFAULT NULL
|
|
)
|
|
RETURNS NUMERIC AS $$
|
|
DECLARE
|
|
standardized_name text;
|
|
measure_id text;
|
|
result Numeric;
|
|
BEGIN
|
|
standardized_name = cdb_observatory._OBS_StandardizeMeasureName(name);
|
|
|
|
EXECUTE $string$
|
|
SELECT c.id
|
|
FROM observatory.obs_column c
|
|
JOIN observatory.obs_column_tag ct
|
|
ON c.id = ct.column_id
|
|
WHERE cdb_observatory._OBS_StandardizeMeasureName(c.name) = $1
|
|
AND ct.tag_id ILIKE 'us.census%'
|
|
$string$
|
|
INTO measure_id
|
|
USING standardized_name;
|
|
|
|
EXECUTE 'SELECT cdb_observatory.OBS_GetMeasure($1, $2, $3, $4, $5)'
|
|
INTO result
|
|
USING geom, measure_id, normalize, boundary_id, time_span;
|
|
RETURN result;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetUSCensusCategory(
|
|
geom geometry(Geometry, 4326),
|
|
name TEXT,
|
|
boundary_id TEXT DEFAULT NULL,
|
|
time_span TEXT DEFAULT NULL
|
|
)
|
|
RETURNS TEXT AS $$
|
|
DECLARE
|
|
standardized_name TEXT;
|
|
category_id TEXT;
|
|
result TEXT;
|
|
BEGIN
|
|
standardized_name = cdb_observatory._OBS_StandardizeMeasureName(name);
|
|
|
|
EXECUTE $string$
|
|
SELECT c.id
|
|
FROM observatory.obs_column c
|
|
--JOIN observatory.obs_column_tag ct
|
|
-- ON c.id = ct.column_id
|
|
WHERE cdb_observatory._OBS_StandardizeMeasureName(c.name) = $1
|
|
AND c.type ILIKE 'TEXT'
|
|
AND c.id ILIKE 'us.census%' -- TODO this should be done by tag
|
|
--AND ct.tag_id = 'us.census.acs.demographics'
|
|
$string$
|
|
INTO category_id
|
|
USING standardized_name;
|
|
|
|
EXECUTE 'SELECT cdb_observatory.OBS_GetCategory($1, $2, $3, $4)'
|
|
INTO result
|
|
USING geom, category_id, boundary_id, time_span;
|
|
|
|
RETURN result;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetPopulation(
|
|
geom geometry(Geometry, 4326),
|
|
normalize TEXT DEFAULT NULL,
|
|
boundary_id TEXT DEFAULT NULL,
|
|
time_span TEXT DEFAULT NULL
|
|
)
|
|
RETURNS NUMERIC
|
|
AS $$
|
|
DECLARE
|
|
population_measure_id TEXT;
|
|
result Numeric;
|
|
BEGIN
|
|
-- TODO use a super-column for global pop
|
|
population_measure_id := 'us.census.acs.B01003001';
|
|
|
|
EXECUTE $query$ SELECT cdb_observatory.OBS_GetMeasure(
|
|
$1, $2, $3, $4, $5
|
|
) LIMIT 1
|
|
$query$
|
|
INTO result
|
|
USING geom, population_measure_id, normalize, boundary_id, time_span;
|
|
|
|
RETURN result;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.OBS_GetSegmentSnapshot(
|
|
geom geometry(Geometry, 4326),
|
|
boundary_id text DEFAULT NULL
|
|
)
|
|
RETURNS JSON
|
|
AS $$
|
|
DECLARE
|
|
meta JSON;
|
|
data JSON;
|
|
result JSON;
|
|
BEGIN
|
|
boundary_id = COALESCE(boundary_id, 'us.census.tiger.census_tract');
|
|
|
|
EXECUTE $query$
|
|
SELECT cdb_observatory.OBS_GetMeta($1, ('[ ' ||
|
|
'{"numer_id": "us.census.acs.B01003001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B01001002_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B01001026_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B01002001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B03002003_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B03002004_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B03002006_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B03002012_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B05001006_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08006001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08006002_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08301010_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08006009_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08006011_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08006015_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B08006017_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B09001001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B11001001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B14001001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B14001002_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B14001005_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B14001006_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B14001007_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B14001008_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B15003001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B15003017_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B15003022_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B15003023_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B16001001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B16001002_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B16001003_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B17001001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B17001002_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19013001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19083001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B19301001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25001001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25002003_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25004002_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25004004_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25058001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25071001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25075001_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.acs.B25075025_quantile", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.spielman_singleton_segments.X10", "geom_id": ' || $2 || '},' ||
|
|
'{"numer_id": "us.census.spielman_singleton_segments.X55", "geom_id": ' || $2 || '}' ||
|
|
']')::JSON)
|
|
$query$
|
|
INTO meta
|
|
USING geom, COALESCE('"' || boundary_id || '"', 'null');
|
|
|
|
EXECUTE $query$
|
|
SELECT data FROM cdb_observatory.OBS_GetData(
|
|
ARRAY[($1, 1)::geomval], $2)
|
|
$query$
|
|
INTO data
|
|
USING geom, meta;
|
|
|
|
EXECUTE $query$
|
|
WITH els AS (SELECT
|
|
REPLACE(REPLACE(JSON_Array_Elements($1)->>'numer_id',
|
|
'us.census.spielman_singleton_segments.X55', 'x55_segment'),
|
|
'us.census.spielman_singleton_segments.X10', 'x10_segment') k,
|
|
JSON_Array_Elements($2)->>'value' v)
|
|
SELECT JSON_Object_Agg(k, v) FROM els
|
|
$query$
|
|
INTO result
|
|
USING meta, data;
|
|
|
|
RETURN result;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
-- MetadataValidation checks the metadata parameters and the geometry type
|
|
-- of the data in order to find possible wrong cases
|
|
CREATE OR REPLACE FUNCTION cdb_observatory.obs_metadatavalidation(
|
|
geometry_extent geometry(Geometry, 4326),
|
|
geometry_type text,
|
|
params JSON,
|
|
target_geoms INTEGER DEFAULT NULL
|
|
)
|
|
RETURNS TABLE(valid boolean, errors text[]) AS $$
|
|
DECLARE
|
|
meta json;
|
|
errors text[];
|
|
BEGIN
|
|
errors := (ARRAY[])::TEXT[];
|
|
IF geometry_type IN ('ST_Polygon', 'ST_MultiPolygon') THEN
|
|
FOR meta IN EXECUTE 'SELECT json_array_elements(cdb_observatory.OBS_GetMeta($1, $2, 1, 1, $3))' USING geometry_extent, params, target_geoms
|
|
LOOP
|
|
IF (meta->>'normalization' = 'denominated' AND meta->>'denom_id' is NULL) THEN
|
|
errors := array_append(errors, 'Normalizated measure should have a numerator and a denominator. Please review the provided options.');
|
|
END IF;
|
|
IF (meta->>'numer_aggregate' IS NULL) THEN
|
|
errors := array_append(errors, 'For polygon geometries, aggregation is mandatory. Please review the provided options');
|
|
END IF;
|
|
IF (meta->>'numer_aggregate' IN ('median', 'average') AND meta->>'denom_id' IS NULL) THEN
|
|
errors := array_append(errors, 'Median or average aggregation for polygons requires a denominator to provide weights. Please review the provided options');
|
|
END IF;
|
|
IF (meta->>'numer_aggregate' IN ('median', 'average') AND meta->>'normalization' NOT LIKE 'pre%') THEN
|
|
errors := array_append(errors, format('Median or average aggregation only supports prenormalized normalization, %s passed. Please review the provided options', meta->>'normalization'));
|
|
END IF;
|
|
END LOOP;
|
|
|
|
IF CARDINALITY(errors) > 0 THEN
|
|
RETURN QUERY EXECUTE 'SELECT FALSE, $1' USING errors;
|
|
ELSE
|
|
RETURN QUERY SELECT TRUE, ARRAY[]::TEXT[];
|
|
END IF;
|
|
ELSE
|
|
RETURN QUERY SELECT TRUE, ARRAY[]::TEXT[];
|
|
END IF;
|
|
RETURN;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|