--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;