diff --git a/doc/07_gravity.md b/doc/07_gravity.md new file mode 100644 index 0000000..e4e439e --- /dev/null +++ b/doc/07_gravity.md @@ -0,0 +1,78 @@ +## Gravity Model + +Gravity Models are derived from Newton's Law of Gravity and are used to predict the interaction between a group of populated areas (sources) and a specific target among a group of potential targets, in terms of an attraction factor (weight) + +**CDB_Gravity** is based on the model defined in *Huff's Law of Shopper attraction (1963)* + +### CDB_Gravity(t_id bigint[], t_geom geometry[], t_weight numeric[], s_id bigint[], s_geom geometry[], s_pop numeric[], target bigint, radius integer, minval numeric DEFAULT -10e307) + +#### Arguments + +| Name | Type | Description | +|------|------|-------------| +| t_id | bigint[] | Array of targets ID | +| t_geom | geometry[] | Array of targets' geometries | +| t_weight | numeric[] | Array of targets's weights | +| s_id | bigint[] | Array of sources ID | +| s_geom | geometry[] | Array of sources' geometries | +| s_pop | numeric[] | Array of sources's population | +| target | bigint | ID of the target under study | +| radius | integer | Radius in meters around the target under study that will be taken into account| +| minval (optional) | numeric | Lowest accepted value of weight, defaults to numeric min_value | + +### CDB_Gravity( target_query text, weight_column text, source_query text, pop_column text, target bigint, radius integer, minval numeric DEFAULT -10e307) + +#### Arguments + +| Name | Type | Description | +|------|------|-------------| +| target_query | text | Query that defines targets | +| weight_column | text | Column name of weights | +| source_query | text | Query that defines sources | +| pop_column | text | Column name of population | +| target | bigint | cartodb_id of the target under study | +| radius | integer | Radius in meters around the target under study that will be taken into account| +| minval (optional) | numeric | Lowest accepted value of weight, defaults to numeric min_value | + + +### Returns + +| Column Name | Type | Description | +|-------------|------|-------------| +| the_geom | geometry | Geometries of the sources within the radius | +| source_id | bigint | ID of the source | +| target_id | bigint | Target ID from input | +| dist | numeric | Distance in meters source to target (if not points, distance between centroids) | +| h | numeric | Probability of patronage | +| hpop | numeric | Patronaging population | + + +#### Example Usage + +```sql +with t as ( +SELECT + array_agg(cartodb_id::bigint) as id, + array_agg(the_geom) as g, + array_agg(coalesce(gla,0)::numeric) as w +FROM + abel.centros_comerciales_de_madrid +WHERE not no_cc +), +s as ( +SELECT + array_agg(cartodb_id::bigint) as id, + array_agg(center) as g, + array_agg(coalesce(t1_1, 0)::numeric) as p +FROM + sscc_madrid +) +select + g.the_geom, + trunc(g.h,2) as h, + round(g.hpop) as hpop, + trunc(g.dist/1000,2) as dist_km +FROM t, s, CDB_Gravity1(t.id, t.g, t.w, s.id, s.g, s.p, newmall_ID, 100000, 5000) g +``` + + diff --git a/src/pg/sql/07_gravity.sql b/src/pg/sql/07_gravity.sql new file mode 100644 index 0000000..47e5b8e --- /dev/null +++ b/src/pg/sql/07_gravity.sql @@ -0,0 +1,115 @@ +CREATE OR REPLACE FUNCTION CDB_Gravity( + IN target_query text, + IN weight_column text, + IN source_query text, + IN pop_column text, + IN target bigint, + IN radius integer, + IN minval numeric DEFAULT -10e307 + ) +RETURNS TABLE( + the_geom geometry, + source_id bigint, + target_id bigint, + dist numeric, + h numeric, + hpop numeric) AS $$ +DECLARE + t_id bigint[]; + t_geom geometry[]; + t_weight numeric[]; + s_id bigint[]; + s_geom geometry[]; + s_pop numeric[]; +BEGIN + EXECUTE 'WITH foo as('+target_query+') SELECT array_agg(cartodb_id), array_agg(the_geom), array_agg(' || weight_column || ') FROM foo' INTO t_id, t_geom, t_weight; + EXECUTE 'WITH foo as('+source_query+') SELECT array_agg(cartodb_id), array_agg(the_geom), array_agg(' || pop_column || ') FROM foo' INTO s_id, s_geom, s_pop; + RETURN QUERY + SELECT g.* FROM t, s, CDB_Gravity(t_id, t_geom, t_weight, s_id, s_geom, s_pop, target, radius, minval) g; +END; +$$ language plpgsql; + +CREATE OR REPLACE FUNCTION CDB_Gravity( + IN t_id bigint[], + IN t_geom geometry[], + IN t_weight numeric[], + IN s_id bigint[], + IN s_geom geometry[], + IN s_pop numeric[], + IN target bigint, + IN radius integer, + IN minval numeric DEFAULT -10e307 + ) +RETURNS TABLE( + the_geom geometry, + source_id bigint, + target_id bigint, + dist numeric, + h numeric, + hpop numeric) AS $$ +DECLARE + t_type text; + s_type text; + t_center geometry[]; + s_center geometry[]; +BEGIN + t_type := GeometryType(t_geom[1]); + s_type := GeometryType(s_geom[1]); + IF t_type = 'POINT' THEN + t_center := t_geom; + ELSE + WITH tmp as (SELECT unnest(t_geom) as g) SELECT array_agg(ST_Centroid(g)) INTO t_center FROM tmp; + END IF; + IF s_type = 'POINT' THEN + s_center := s_geom; + ELSE + WITH tmp as (SELECT unnest(s_geom) as g) SELECT array_agg(ST_Centroid(g)) INTO s_center FROM tmp; + END IF; + RETURN QUERY + with target0 as( + SELECT unnest(t_center) as tc, unnest(t_weight) as tw, unnest(t_id) as td + ), + source0 as( + SELECT unnest(s_center) as sc, unnest(s_id) as sd, unnest (s_geom) as sg, unnest(s_pop) as sp + ), + prev0 as( + SELECT + source0.sg, + source0.sd as sourc_id, + coalesce(source0.sp,0) as sp, + target.td as targ_id, + coalesce(target.tw,0) as tw, + GREATEST(1.0,ST_Distance(geography(target.tc), geography(source0.sc)))::numeric as distance + FROM source0 + CROSS JOIN LATERAL + ( + SELECT + * + FROM target0 + WHERE tw > minval + AND ST_DWithin(geography(source0.sc), geography(tc), radius) + ) AS target + ), + deno as( + SELECT + sourc_id, + sum(tw/distance) as h_deno + FROM + prev0 + GROUP BY sourc_id + ) + SELECT + p.sg as the_geom, + p.sourc_id as source_id, + p.targ_id as target_id, + case when p.distance > 1 then p.distance else 0.0 end as dist, + 100*(p.tw/p.distance)/d.h_deno as h, + p.sp*(p.tw/p.distance)/d.h_deno as hpop + FROM + prev0 p, + deno d + WHERE + p.targ_id = target AND + p.sourc_id = d.sourc_id; +END; +$$ language plpgsql; diff --git a/src/pg/test/expected/07_gravity_test.out b/src/pg/test/expected/07_gravity_test.out new file mode 100644 index 0000000..c101b24 --- /dev/null +++ b/src/pg/test/expected/07_gravity_test.out @@ -0,0 +1,11 @@ + the_geom | h | hpop | dist +--------------------------------------------+-------------------------+--------------------------+---------------- + 01010000001361C3D32B650140DD24068195B34440 | 1.51078258369747945249 | 12.08626066957983561994 | 4964.714459152 + 01010000002497FF907EFB0040713D0AD7A3B04440 | 98.29730954183620807430 | 688.08116679285345652007 | 99.955141922 + 0101000000A167B3EA733501401D5A643BDFAF4440 | 63.70532894711274639196 | 382.23197368267647835174 | 2488.330566505 + 010100000062A1D634EF380140BE9F1A2FDDB44440 | 35.35415870080995954879 | 176.77079350404979774397 | 4359.370460594 + 010100000052B81E85EB510140355EBA490CB24440 | 33.12290506987740864904 | 132.49162027950963459615 | 3703.664449828 + 0101000000C286A757CA320140736891ED7CAF4440 | 65.45251754279248087849 | 196.35755262837744263547 | 2512.092358644 + 01010000007DD0B359F5390140C976BE9F1AAF4440 | 62.83927792471345639225 | 125.67855584942691278449 | 2926.25725244 + 0101000000D237691A140D01407E6FD39FFDB44440 | 53.54905726651871279586 | 53.54905726651871279586 | 3744.515577777 +(8 rows) diff --git a/src/pg/test/sql/07_gravity_test.sql b/src/pg/test/sql/07_gravity_test.sql new file mode 100644 index 0000000..a86bb23 --- /dev/null +++ b/src/pg/test/sql/07_gravity_test.sql @@ -0,0 +1,21 @@ +WITH t AS ( + SELECT + ARRAY[1,2,3] AS id, + ARRAY[7.0,8.0,3.0] AS w, + ARRAY[ST_GeomFromText('POINT(2.1744 41.4036)'),ST_GeomFromText('POINT(2.1228 41.3809)'),ST_GeomFromText('POINT(2.1511 41.3742)')] AS g +), +s AS ( + SELECT + ARRAY[10,20,30,40,50,60,70,80] AS id, + ARRAY[800, 700, 600, 500, 400, 300, 200, 100] AS p, + ARRAY[ST_GeomFromText('POINT(2.1744 41.403)'),ST_GeomFromText('POINT(2.1228 41.380)'),ST_GeomFromText('POINT(2.1511 41.374)'),ST_GeomFromText('POINT(2.1528 41.413)'),ST_GeomFromText('POINT(2.165 41.391)'),ST_GeomFromText('POINT(2.1498 41.371)'),ST_GeomFromText('POINT(2.1533 41.368)'),ST_GeomFromText('POINT(2.131386 41.41399)')] AS g +) +SELECT + g.the_geom, + g.h, + g.hpop, + g.dist +FROM + t, + s, + CDB_Gravity(t.id, t.g, t.w, s.id, s.g, s.p, 2, 100000, 3) g;