# HG changeset patch # User jbe # Date 1477413883 -7200 # Node ID 1b9cd45e9e48b9322c234d4fba7f548e1d783356 # Parent a90c0d750405ad2fd6148759b26dac1e51ca515e Bugfix for type casts to ecluster; New "fair_distance" function diff -r a90c0d750405 -r 1b9cd45e9e48 GNUmakefile --- a/GNUmakefile Fri Oct 21 12:57:46 2016 +0200 +++ b/GNUmakefile Tue Oct 25 18:44:43 2016 +0200 @@ -1,6 +1,6 @@ EXTENSION = latlon -DATA = latlon--0.7--0.8.sql latlon--0.8.sql -MODULES = latlon-v0007 +DATA = latlon--0.7--0.8.sql latlon--0.8.sql latlon--0.9.sql +MODULES = latlon-v0007 latlon-v0008 PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) diff -r a90c0d750405 -r 1b9cd45e9e48 README.html --- a/README.html Fri Oct 21 12:57:46 2016 +0200 +++ b/README.html Tue Oct 25 18:44:43 2016 +0200 @@ -1,5 +1,5 @@ -pgLatLon v0.8 documentation -

pgLatLon v0.8 documentation

+pgLatLon v0.9 documentation +

pgLatLon v0.9 documentation

pgLatLon is a spatial database extension for the PostgreSQL object-relational database management system providing geographic data types and spatial indexing @@ -40,8 +40,8 @@

It is also possible to compile and install the extension without GNU Make as follows:

-
cc -Wall -O2 -fPIC -shared -I `pg_config --includedir-server` -o latlon-v0007.so latlon-v0007.c
-cp latlon-v0007.so `pg_config --pkglibdir`
+
cc -Wall -O2 -fPIC -shared -I `pg_config --includedir-server` -o latlon-v0008.so latlon-v0008.c
+cp latlon-v0008.so `pg_config --pkglibdir`
 cp latlon.control `pg_config --sharedir`/extension/
 cp latlon--*.sql `pg_config --sharedir`/extension/
 
@@ -486,6 +486,40 @@

Same as epoint(float8, float8) but with arguments reversed.

+

fair_distance(ecluster, epoint, samples int4 = 10000)

+ +

When working with user-generated content, users may be tempted to create +intentionally oversized objects in order to optimize search results in an +unfair manner. The fair_distance function aims to handle this by returning an +adjusted distance (i.e. distance increased by a penalty) if a geographic object +(the ecluster) consists of more than one point.

+ +

The first argument to this function is an ecluster, the second argument is a +search point (epoint), and the third argument is an interger related to the +precision (higher precision will require more computation time).

+ +

The penalty by which the returned distance is increased fulfills (at least) the +following properties:

+ + + +

The function interally uses a Monte Carlo simulation to compute the result. The +third parameter (which defaults to 10000) can be used to adjust the number of +samples taken. It is ensured that the penalty is always positive, i.e. results +returned by the fair_distance function are always equal to or greater than +the results returned by the distance function regardless of stochastic +effects.

+

GeoJSON_to_epoint(jsonb, text)

Maps a GeoJSON object of type "Point" or "Feature" (which contains a diff -r a90c0d750405 -r 1b9cd45e9e48 README.mkd --- a/README.mkd Fri Oct 21 12:57:46 2016 +0200 +++ b/README.mkd Tue Oct 25 18:44:43 2016 +0200 @@ -1,4 +1,4 @@ -pgLatLon v0.8 documentation +pgLatLon v0.9 documentation =========================== pgLatLon is a spatial database extension for the PostgreSQL object-relational @@ -39,8 +39,8 @@ It is also possible to compile and install the extension without GNU Make as follows: - cc -Wall -O2 -fPIC -shared -I `pg_config --includedir-server` -o latlon-v0007.so latlon-v0007.c - cp latlon-v0007.so `pg_config --pkglibdir` + cc -Wall -O2 -fPIC -shared -I `pg_config --includedir-server` -o latlon-v0008.so latlon-v0008.c + cp latlon-v0008.so `pg_config --pkglibdir` cp latlon.control `pg_config --sharedir`/extension/ cp latlon--*.sql `pg_config --sharedir`/extension/ @@ -469,6 +469,38 @@ Same as `epoint(float8, float8)` but with arguments reversed. +#### `fair_distance(ecluster, epoint,` samples `int4 = 10000)` + +When working with user-generated content, users may be tempted to create +intentionally oversized objects in order to optimize search results in an +unfair manner. The `fair_distance` function aims to handle this by returning an +adjusted distance (i.e. distance increased by a penalty) if a geographic object +(the `ecluster`) consists of more than one point. + +The first argument to this function is an `ecluster`, the second argument is a +search point (`epoint`), and the third argument is an interger related to the +precision (higher precision will require more computation time). + +The penalty by which the returned distance is increased fulfills (at least) the +following properties: + +* For search points far away from the `ecluster` (i.e. distance approaching + infinity), the penalty approaches zero (i.e. `fair_distance` behaves the same + as `distance`). +* If the `ecluster` consists of a set of points, the penalty for a search point + close to one of that points (closer than half of the minimum distance between + each pair of points in the `ecluster`) is chosen in such a way that the + adjusted distance is equal to the distance from the search point to the + closest point in the `ecluster` multiplied by the square root of the count of + points in the `ecluster`. + +The function interally uses a Monte Carlo simulation to compute the result. The +third parameter (which defaults to 10000) can be used to adjust the number of +samples taken. It is ensured that the penalty is always positive, i.e. results +returned by the `fair_distance` function are always equal to or greater than +the results returned by the `distance` function regardless of stochastic +effects. + #### `GeoJSON_to_epoint(jsonb, text)` Maps a GeoJSON object of type "Point" or "Feature" (which contains a diff -r a90c0d750405 -r 1b9cd45e9e48 latlon--0.9.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/latlon--0.9.sql Tue Oct 25 18:44:43 2016 +0200 @@ -0,0 +1,1692 @@ + +---------------------------------------- +-- forward declarations (shell types) -- +---------------------------------------- + +CREATE TYPE epoint; +CREATE TYPE ebox; +CREATE TYPE ecircle; +CREATE TYPE ecluster; + + +------------------------------------------------------------ +-- dummy input/output functions for dummy index key types -- +------------------------------------------------------------ + +CREATE FUNCTION ekey_point_in_dummy(cstring) + RETURNS ekey_point + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_notimpl'; + +CREATE FUNCTION ekey_point_out_dummy(ekey_point) + RETURNS cstring + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_notimpl'; + +CREATE FUNCTION ekey_area_in_dummy(cstring) + RETURNS ekey_area + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_notimpl'; + +CREATE FUNCTION ekey_area_out_dummy(ekey_area) + RETURNS cstring + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_notimpl'; + + +-------------------------- +-- text input functions -- +-------------------------- + +CREATE FUNCTION epoint_in(cstring) + RETURNS epoint + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_in'; + +CREATE FUNCTION ebox_in(cstring) + RETURNS ebox + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ebox_in'; + +CREATE FUNCTION ecircle_in(cstring) + RETURNS ecircle + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecircle_in'; + +CREATE FUNCTION ecluster_in(cstring) + RETURNS ecluster + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecluster_in'; + + +--------------------------- +-- text output functions -- +--------------------------- + +CREATE FUNCTION epoint_out(epoint) + RETURNS cstring + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_out'; + +CREATE FUNCTION ebox_out(ebox) + RETURNS cstring + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ebox_out'; + +CREATE FUNCTION ecircle_out(ecircle) + RETURNS cstring + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecircle_out'; + +CREATE FUNCTION ecluster_out(ecluster) + RETURNS cstring + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecluster_out'; + + +-------------------------- +-- binary I/O functions -- +-------------------------- + +CREATE FUNCTION epoint_recv(internal) + RETURNS epoint + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_recv'; + +CREATE FUNCTION ebox_recv(internal) + RETURNS ebox + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ebox_recv'; + +CREATE FUNCTION ecircle_recv(internal) + RETURNS ecircle + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecircle_recv'; + +CREATE FUNCTION epoint_send(epoint) + RETURNS bytea + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_send'; + +CREATE FUNCTION ebox_send(ebox) + RETURNS bytea + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ebox_send'; + +CREATE FUNCTION ecircle_send(ecircle) + RETURNS bytea + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecircle_send'; + + +----------------------------------------------- +-- type definitions of dummy index key types -- +----------------------------------------------- + +CREATE TYPE ekey_point ( + internallength = 8, + input = ekey_point_in_dummy, + output = ekey_point_out_dummy, + alignment = char ); + +CREATE TYPE ekey_area ( + internallength = 9, + input = ekey_area_in_dummy, + output = ekey_area_out_dummy, + alignment = char ); + + +------------------------------------------ +-- definitions of geographic data types -- +------------------------------------------ + +CREATE TYPE epoint ( + internallength = 16, + input = epoint_in, + output = epoint_out, + receive = epoint_recv, + send = epoint_send, + alignment = double ); + +CREATE TYPE ebox ( + internallength = 32, + input = ebox_in, + output = ebox_out, + receive = ebox_recv, + send = ebox_send, + alignment = double ); + +CREATE TYPE ecircle ( + internallength = 24, + input = ecircle_in, + output = ecircle_out, + receive = ecircle_recv, + send = ecircle_send, + alignment = double ); + +CREATE TYPE ecluster ( + internallength = VARIABLE, + input = ecluster_in, + output = ecluster_out, + alignment = double, + storage = external ); + + +-------------------- +-- B-tree support -- +-------------------- + +-- begin of B-tree support for epoint + +CREATE FUNCTION epoint_btree_lt(epoint, epoint) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_epoint_lt'; + +CREATE FUNCTION epoint_btree_le(epoint, epoint) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_epoint_le'; + +CREATE FUNCTION epoint_btree_eq(epoint, epoint) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_epoint_eq'; + +CREATE FUNCTION epoint_btree_ne(epoint, epoint) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_epoint_ne'; + +CREATE FUNCTION epoint_btree_ge(epoint, epoint) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_epoint_ge'; + +CREATE FUNCTION epoint_btree_gt(epoint, epoint) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_epoint_gt'; + +CREATE OPERATOR <<< ( + leftarg = epoint, + rightarg = epoint, + procedure = epoint_btree_lt, + commutator = >>>, + negator = >>>=, + restrict = scalarltsel, + join = scalarltjoinsel +); + +CREATE OPERATOR <<<= ( + leftarg = epoint, + rightarg = epoint, + procedure = epoint_btree_le, + commutator = >>>=, + negator = >>>, + restrict = scalarltsel, + join = scalarltjoinsel +); + +CREATE OPERATOR = ( + leftarg = epoint, + rightarg = epoint, + procedure = epoint_btree_eq, + commutator = =, + negator = <>, + restrict = eqsel, + join = eqjoinsel, + merges +); + +CREATE OPERATOR <> ( + leftarg = epoint, + rightarg = epoint, + procedure = epoint_btree_eq, + commutator = <>, + negator = =, + restrict = neqsel, + join = neqjoinsel +); + +CREATE OPERATOR >>>= ( + leftarg = epoint, + rightarg = epoint, + procedure = epoint_btree_ge, + commutator = <<<=, + negator = <<<, + restrict = scalargtsel, + join = scalargtjoinsel +); + +CREATE OPERATOR >>> ( + leftarg = epoint, + rightarg = epoint, + procedure = epoint_btree_gt, + commutator = <<<, + negator = <<<=, + restrict = scalargtsel, + join = scalargtjoinsel +); + +CREATE FUNCTION epoint_btree_cmp(epoint, epoint) + RETURNS int4 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_epoint_cmp'; + +CREATE OPERATOR CLASS epoint_btree_ops + DEFAULT FOR TYPE epoint USING btree AS + OPERATOR 1 <<< , + OPERATOR 2 <<<= , + OPERATOR 3 = , + OPERATOR 4 >>>= , + OPERATOR 5 >>> , + FUNCTION 1 epoint_btree_cmp(epoint, epoint); + +-- end of B-tree support for epoint + +-- begin of B-tree support for ebox + +CREATE FUNCTION ebox_btree_lt(ebox, ebox) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ebox_lt'; + +CREATE FUNCTION ebox_btree_le(ebox, ebox) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ebox_le'; + +CREATE FUNCTION ebox_btree_eq(ebox, ebox) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ebox_eq'; + +CREATE FUNCTION ebox_btree_ne(ebox, ebox) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ebox_ne'; + +CREATE FUNCTION ebox_btree_ge(ebox, ebox) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ebox_ge'; + +CREATE FUNCTION ebox_btree_gt(ebox, ebox) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ebox_gt'; + +CREATE OPERATOR <<< ( + leftarg = ebox, + rightarg = ebox, + procedure = ebox_btree_lt, + commutator = >>>, + negator = >>>=, + restrict = scalarltsel, + join = scalarltjoinsel +); + +CREATE OPERATOR <<<= ( + leftarg = ebox, + rightarg = ebox, + procedure = ebox_btree_le, + commutator = >>>=, + negator = >>>, + restrict = scalarltsel, + join = scalarltjoinsel +); + +CREATE OPERATOR = ( + leftarg = ebox, + rightarg = ebox, + procedure = ebox_btree_eq, + commutator = =, + negator = <>, + restrict = eqsel, + join = eqjoinsel, + merges +); + +CREATE OPERATOR <> ( + leftarg = ebox, + rightarg = ebox, + procedure = ebox_btree_eq, + commutator = <>, + negator = =, + restrict = neqsel, + join = neqjoinsel +); + +CREATE OPERATOR >>>= ( + leftarg = ebox, + rightarg = ebox, + procedure = ebox_btree_ge, + commutator = <<<=, + negator = <<<, + restrict = scalargtsel, + join = scalargtjoinsel +); + +CREATE OPERATOR >>> ( + leftarg = ebox, + rightarg = ebox, + procedure = ebox_btree_gt, + commutator = <<<, + negator = <<<=, + restrict = scalargtsel, + join = scalargtjoinsel +); + +CREATE FUNCTION ebox_btree_cmp(ebox, ebox) + RETURNS int4 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ebox_cmp'; + +CREATE OPERATOR CLASS ebox_btree_ops + DEFAULT FOR TYPE ebox USING btree AS + OPERATOR 1 <<< , + OPERATOR 2 <<<= , + OPERATOR 3 = , + OPERATOR 4 >>>= , + OPERATOR 5 >>> , + FUNCTION 1 ebox_btree_cmp(ebox, ebox); + +-- end of B-tree support for ebox + +-- begin of B-tree support for ecircle + +CREATE FUNCTION ecircle_btree_lt(ecircle, ecircle) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ecircle_lt'; + +CREATE FUNCTION ecircle_btree_le(ecircle, ecircle) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ecircle_le'; + +CREATE FUNCTION ecircle_btree_eq(ecircle, ecircle) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ecircle_eq'; + +CREATE FUNCTION ecircle_btree_ne(ecircle, ecircle) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ecircle_ne'; + +CREATE FUNCTION ecircle_btree_ge(ecircle, ecircle) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ecircle_ge'; + +CREATE FUNCTION ecircle_btree_gt(ecircle, ecircle) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ecircle_gt'; + +CREATE OPERATOR <<< ( + leftarg = ecircle, + rightarg = ecircle, + procedure = ecircle_btree_lt, + commutator = >>>, + negator = >>>=, + restrict = scalarltsel, + join = scalarltjoinsel +); + +CREATE OPERATOR <<<= ( + leftarg = ecircle, + rightarg = ecircle, + procedure = ecircle_btree_le, + commutator = >>>=, + negator = >>>, + restrict = scalarltsel, + join = scalarltjoinsel +); + +CREATE OPERATOR = ( + leftarg = ecircle, + rightarg = ecircle, + procedure = ecircle_btree_eq, + commutator = =, + negator = <>, + restrict = eqsel, + join = eqjoinsel, + merges +); + +CREATE OPERATOR <> ( + leftarg = ecircle, + rightarg = ecircle, + procedure = ecircle_btree_eq, + commutator = <>, + negator = =, + restrict = neqsel, + join = neqjoinsel +); + +CREATE OPERATOR >>>= ( + leftarg = ecircle, + rightarg = ecircle, + procedure = ecircle_btree_ge, + commutator = <<<=, + negator = <<<, + restrict = scalargtsel, + join = scalargtjoinsel +); + +CREATE OPERATOR >>> ( + leftarg = ecircle, + rightarg = ecircle, + procedure = ecircle_btree_gt, + commutator = <<<, + negator = <<<=, + restrict = scalargtsel, + join = scalargtjoinsel +); + +CREATE FUNCTION ecircle_btree_cmp(ecircle, ecircle) + RETURNS int4 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_btree_ecircle_cmp'; + +CREATE OPERATOR CLASS ecircle_btree_ops + DEFAULT FOR TYPE ecircle USING btree AS + OPERATOR 1 <<< , + OPERATOR 2 <<<= , + OPERATOR 3 = , + OPERATOR 4 >>>= , + OPERATOR 5 >>> , + FUNCTION 1 ecircle_btree_cmp(ecircle, ecircle); + +-- end of B-tree support for ecircle + + +---------------- +-- type casts -- +---------------- + +CREATE FUNCTION cast_epoint_to_ebox(epoint) + RETURNS ebox + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_to_ebox'; + +CREATE CAST (epoint AS ebox) WITH FUNCTION cast_epoint_to_ebox(epoint); + +CREATE FUNCTION cast_epoint_to_ecircle(epoint) + RETURNS ecircle + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_to_ecircle'; + +CREATE CAST (epoint AS ecircle) WITH FUNCTION cast_epoint_to_ecircle(epoint); + +CREATE FUNCTION cast_epoint_to_ecluster(epoint) + RETURNS ecluster + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_to_ecluster'; + +CREATE CAST (epoint AS ecluster) WITH FUNCTION cast_epoint_to_ecluster(epoint); + +CREATE FUNCTION cast_ebox_to_ecluster(ebox) + RETURNS ecluster + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ebox_to_ecluster'; + +CREATE CAST (ebox AS ecluster) WITH FUNCTION cast_ebox_to_ecluster(ebox); + + +--------------------------- +-- constructor functions -- +--------------------------- + +CREATE FUNCTION epoint(float8, float8) + RETURNS epoint + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_create_epoint'; + +CREATE FUNCTION epoint_latlon(float8, float8) + RETURNS epoint + LANGUAGE SQL IMMUTABLE STRICT AS $$ + SELECT epoint($1, $2) + $$; + +CREATE FUNCTION epoint_lonlat(float8, float8) + RETURNS epoint + LANGUAGE SQL IMMUTABLE STRICT AS $$ + SELECT epoint($2, $1) + $$; + +CREATE FUNCTION empty_ebox() + RETURNS ebox + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_create_empty_ebox'; + +CREATE FUNCTION ebox(float8, float8, float8, float8) + RETURNS ebox + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_create_ebox'; + +CREATE FUNCTION ebox(epoint, epoint) + RETURNS ebox + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_create_ebox_from_epoints'; + +CREATE FUNCTION ecircle(float8, float8, float8) + RETURNS ecircle + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_create_ecircle'; + +CREATE FUNCTION ecircle(epoint, float8) + RETURNS ecircle + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_create_ecircle_from_epoint'; + +CREATE FUNCTION ecluster_concat(ecluster[]) + RETURNS ecluster + LANGUAGE sql IMMUTABLE STRICT AS $$ + SELECT array_to_string($1, ' ')::ecluster + $$; + +CREATE FUNCTION ecluster_concat(ecluster, ecluster) + RETURNS ecluster + LANGUAGE sql IMMUTABLE STRICT AS $$ + SELECT ($1::text || ' ' || $2::text)::ecluster + $$; + +CREATE FUNCTION ecluster_create_multipoint(epoint[]) + RETURNS ecluster + LANGUAGE sql IMMUTABLE STRICT AS $$ + SELECT + array_to_string(array_agg('point (' || unnest || ')'), ' ')::ecluster + FROM unnest($1) + $$; + +CREATE FUNCTION ecluster_create_path(epoint[]) + RETURNS ecluster + LANGUAGE sql IMMUTABLE STRICT AS $$ + SELECT CASE WHEN "str" = '' THEN 'empty'::ecluster ELSE + ('path (' || array_to_string($1, ' ') || ')')::ecluster + END + FROM array_to_string($1, ' ') AS "str" + $$; + +CREATE FUNCTION ecluster_create_outline(epoint[]) + RETURNS ecluster + LANGUAGE sql IMMUTABLE STRICT AS $$ + SELECT CASE WHEN "str" = '' THEN 'empty'::ecluster ELSE + ('outline (' || array_to_string($1, ' ') || ')')::ecluster + END + FROM array_to_string($1, ' ') AS "str" + $$; + +CREATE FUNCTION ecluster_create_polygon(epoint[]) + RETURNS ecluster + LANGUAGE sql IMMUTABLE STRICT AS $$ + SELECT CASE WHEN "str" = '' THEN 'empty'::ecluster ELSE + ('polygon (' || array_to_string($1, ' ') || ')')::ecluster + END + FROM array_to_string($1, ' ') AS "str" + $$; + + +---------------------- +-- getter functions -- +---------------------- + +CREATE FUNCTION latitude(epoint) + RETURNS float8 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_lat'; + +CREATE FUNCTION longitude(epoint) + RETURNS float8 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_lon'; + +CREATE FUNCTION min_latitude(ebox) + RETURNS float8 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ebox_lat_min'; + +CREATE FUNCTION max_latitude(ebox) + RETURNS float8 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ebox_lat_max'; + +CREATE FUNCTION min_longitude(ebox) + RETURNS float8 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ebox_lon_min'; + +CREATE FUNCTION max_longitude(ebox) + RETURNS float8 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ebox_lon_max'; + +CREATE FUNCTION center(ecircle) + RETURNS epoint + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecircle_center'; + +CREATE FUNCTION radius(ecircle) + RETURNS float8 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecircle_radius'; + +CREATE FUNCTION ecluster_extract_points(ecluster) + RETURNS SETOF epoint + LANGUAGE sql IMMUTABLE STRICT AS $$ + SELECT "match"[2]::epoint + FROM regexp_matches($1::text, e'(^| )point \\(([^)]+)\\)', 'g') AS "match" + $$; + +CREATE FUNCTION ecluster_extract_paths(ecluster) + RETURNS SETOF epoint[] + LANGUAGE sql IMMUTABLE STRICT AS $$ + SELECT ( + SELECT array_agg("m2"[1]::epoint) + FROM regexp_matches("m1"[2], e'[^ ]+ [^ ]+', 'g') AS "m2" + ) + FROM regexp_matches($1::text, e'(^| )path \\(([^)]+)\\)', 'g') AS "m1" + $$; + +CREATE FUNCTION ecluster_extract_outlines(ecluster) + RETURNS SETOF epoint[] + LANGUAGE sql IMMUTABLE STRICT AS $$ + SELECT ( + SELECT array_agg("m2"[1]::epoint) + FROM regexp_matches("m1"[2], e'[^ ]+ [^ ]+', 'g') AS "m2" + ) + FROM regexp_matches($1::text, e'(^| )outline \\(([^)]+)\\)', 'g') AS "m1" + $$; + +CREATE FUNCTION ecluster_extract_polygons(ecluster) + RETURNS SETOF epoint[] + LANGUAGE sql IMMUTABLE STRICT AS $$ + SELECT ( + SELECT array_agg("m2"[1]::epoint) + FROM regexp_matches("m1"[2], e'[^ ]+ [^ ]+', 'g') AS "m2" + ) + FROM regexp_matches($1::text, e'(^| )polygon \\(([^)]+)\\)', 'g') AS "m1" + $$; + + +--------------- +-- operators -- +--------------- + +CREATE FUNCTION epoint_ebox_overlap_proc(epoint, ebox) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_ebox_overlap'; + +CREATE FUNCTION epoint_ecircle_overlap_proc(epoint, ecircle) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_ecircle_overlap'; + +CREATE FUNCTION epoint_ecluster_overlap_proc(epoint, ecluster) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_ecluster_overlap'; + +CREATE FUNCTION epoint_ecluster_may_overlap_proc(epoint, ecluster) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_ecluster_may_overlap'; + +CREATE FUNCTION ebox_overlap_proc(ebox, ebox) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ebox_overlap'; + +CREATE FUNCTION ebox_ecircle_may_overlap_proc(ebox, ecircle) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ebox_ecircle_may_overlap'; + +CREATE FUNCTION ebox_ecluster_may_overlap_proc(ebox, ecluster) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ebox_ecluster_may_overlap'; + +CREATE FUNCTION ecircle_overlap_proc(ecircle, ecircle) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecircle_overlap'; + +CREATE FUNCTION ecircle_ecluster_overlap_proc(ecircle, ecluster) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecircle_ecluster_overlap'; + +CREATE FUNCTION ecircle_ecluster_may_overlap_proc(ecircle, ecluster) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecircle_ecluster_may_overlap'; + +CREATE FUNCTION ecluster_overlap_proc(ecluster, ecluster) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecluster_overlap'; + +CREATE FUNCTION ecluster_may_overlap_proc(ecluster, ecluster) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecluster_may_overlap'; + +CREATE FUNCTION ecluster_contains_proc(ecluster, ecluster) + RETURNS boolean + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecluster_contains'; + +CREATE FUNCTION epoint_distance_proc(epoint, epoint) + RETURNS float8 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_distance'; + +CREATE FUNCTION epoint_ecircle_distance_proc(epoint, ecircle) + RETURNS float8 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_ecircle_distance'; + +CREATE FUNCTION epoint_ecluster_distance_proc(epoint, ecluster) + RETURNS float8 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_epoint_ecluster_distance'; + +CREATE FUNCTION ecircle_distance_proc(ecircle, ecircle) + RETURNS float8 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecircle_distance'; + +CREATE FUNCTION ecircle_ecluster_distance_proc(ecircle, ecluster) + RETURNS float8 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecircle_ecluster_distance'; + +CREATE FUNCTION ecluster_distance_proc(ecluster, ecluster) + RETURNS float8 + LANGUAGE C IMMUTABLE STRICT + AS '$libdir/latlon-v0008', 'pgl_ecluster_distance'; + +CREATE OPERATOR && ( + leftarg = epoint, + rightarg = ebox, + procedure = epoint_ebox_overlap_proc, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION epoint_ebox_overlap_commutator(ebox, epoint) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $2 && $1'; + +CREATE OPERATOR && ( + leftarg = ebox, + rightarg = epoint, + procedure = epoint_ebox_overlap_commutator, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR && ( + leftarg = epoint, + rightarg = ecircle, + procedure = epoint_ecircle_overlap_proc, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION epoint_ecircle_overlap_commutator(ecircle, epoint) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $2 && $1'; + +CREATE OPERATOR && ( + leftarg = ecircle, + rightarg = epoint, + procedure = epoint_ecircle_overlap_commutator, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR && ( + leftarg = epoint, + rightarg = ecluster, + procedure = epoint_ecluster_overlap_proc, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION epoint_ecluster_overlap_commutator(ecluster, epoint) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $2 && $1'; + +CREATE OPERATOR && ( + leftarg = ecluster, + rightarg = epoint, + procedure = epoint_ecluster_overlap_commutator, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR && ( + leftarg = ebox, + rightarg = ebox, + procedure = ebox_overlap_proc, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR && ( + leftarg = ecircle, + rightarg = ecircle, + procedure = ecircle_overlap_proc, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR && ( + leftarg = ecircle, + rightarg = ecluster, + procedure = ecircle_ecluster_overlap_proc, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ecircle_ecluster_overlap_commutator(ecluster, ecircle) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $2 && $1'; + +CREATE OPERATOR && ( + leftarg = ecluster, + rightarg = ecircle, + procedure = ecircle_ecluster_overlap_commutator, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR && ( + leftarg = ecluster, + rightarg = ecluster, + procedure = ecluster_overlap_proc, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ebox_ecircle_overlap_castwrap(ebox, ecircle) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $1::ecluster && $2'; + +CREATE OPERATOR && ( + leftarg = ebox, + rightarg = ecircle, + procedure = ebox_ecircle_overlap_castwrap, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ebox_ecircle_overlap_castwrap(ecircle, ebox) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $1 && $2::ecluster'; + +CREATE OPERATOR && ( + leftarg = ecircle, + rightarg = ebox, + procedure = ebox_ecircle_overlap_castwrap, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ebox_ecluster_overlap_castwrap(ebox, ecluster) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $1::ecluster && $2'; + +CREATE OPERATOR && ( + leftarg = ebox, + rightarg = ecluster, + procedure = ebox_ecluster_overlap_castwrap, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ebox_ecluster_overlap_castwrap(ecluster, ebox) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $1 && $2::ecluster'; + +CREATE OPERATOR && ( + leftarg = ecluster, + rightarg = ebox, + procedure = ebox_ecluster_overlap_castwrap, + commutator = &&, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR &&+ ( + leftarg = epoint, + rightarg = ecluster, + procedure = epoint_ecluster_may_overlap_proc, + commutator = &&+, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION epoint_ecluster_may_overlap_commutator(ecluster, epoint) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $2 &&+ $1'; + +CREATE OPERATOR &&+ ( + leftarg = ecluster, + rightarg = epoint, + procedure = epoint_ecluster_may_overlap_commutator, + commutator = &&+, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR &&+ ( + leftarg = ebox, + rightarg = ecircle, + procedure = ebox_ecircle_may_overlap_proc, + commutator = &&+, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ebox_ecircle_may_overlap_commutator(ecircle, ebox) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $2 &&+ $1'; + +CREATE OPERATOR &&+ ( + leftarg = ecircle, + rightarg = ebox, + procedure = ebox_ecircle_may_overlap_commutator, + commutator = &&+, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR &&+ ( + leftarg = ebox, + rightarg = ecluster, + procedure = ebox_ecluster_may_overlap_proc, + commutator = &&+, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ebox_ecluster_may_overlap_commutator(ecluster, ebox) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $2 &&+ $1'; + +CREATE OPERATOR &&+ ( + leftarg = ecluster, + rightarg = ebox, + procedure = ebox_ecluster_may_overlap_commutator, + commutator = &&+, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR &&+ ( + leftarg = ecircle, + rightarg = ecluster, + procedure = ecircle_ecluster_may_overlap_proc, + commutator = &&+, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ecircle_ecluster_may_overlap_commutator(ecluster, ecircle) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $2 &&+ $1'; + +CREATE OPERATOR &&+ ( + leftarg = ecluster, + rightarg = ecircle, + procedure = ecircle_ecluster_may_overlap_commutator, + commutator = &&+, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR &&+ ( + leftarg = ecluster, + rightarg = ecluster, + procedure = ecluster_may_overlap_proc, + commutator = &&+, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR @> ( + leftarg = ebox, + rightarg = epoint, + procedure = epoint_ebox_overlap_commutator, + commutator = <@, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR <@ ( + leftarg = epoint, + rightarg = ebox, + procedure = epoint_ebox_overlap_proc, + commutator = @>, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR @> ( + leftarg = ecluster, + rightarg = epoint, + procedure = epoint_ecluster_overlap_commutator, + commutator = <@, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR <@ ( + leftarg = epoint, + rightarg = ecluster, + procedure = epoint_ecluster_overlap_proc, + commutator = <@, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR @> ( + leftarg = ecluster, + rightarg = ecluster, + procedure = ecluster_contains_proc, + commutator = <@, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ecluster_contains_commutator(ecluster, ecluster) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $2 @> $1'; + +CREATE OPERATOR <@ ( + leftarg = ecluster, + rightarg = ecluster, + procedure = ecluster_contains_commutator, + commutator = @>, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ebox_contains_castwrap(ebox, ebox) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $1::ecluster @> $2::ecluster'; + +CREATE OPERATOR @> ( + leftarg = ebox, + rightarg = ebox, + procedure = ebox_contains_castwrap, + commutator = <@, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ebox_contains_swapped_castwrap(ebox, ebox) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $2::ecluster @> $1::ecluster'; + +CREATE OPERATOR <@ ( + leftarg = ebox, + rightarg = ebox, + procedure = ebox_contains_swapped_castwrap, + commutator = @>, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ebox_ecluster_contains_castwrap(ebox, ecluster) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $1::ecluster @> $2'; + +CREATE OPERATOR @> ( + leftarg = ebox, + rightarg = ecluster, + procedure = ebox_ecluster_contains_castwrap, + commutator = <@, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ebox_ecluster_contains_castwrap(ecluster, ebox) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $2::ecluster @> $1'; + +CREATE OPERATOR <@ ( + leftarg = ecluster, + rightarg = ebox, + procedure = ebox_ecluster_contains_castwrap, + commutator = @>, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ecluster_ebox_contains_castwrap(ecluster, ebox) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $1 @> $2::ecluster'; + +CREATE OPERATOR @> ( + leftarg = ecluster, + rightarg = ebox, + procedure = ecluster_ebox_contains_castwrap, + commutator = <@, + restrict = areasel, + join = areajoinsel +); + +CREATE FUNCTION ecluster_ebox_contains_castwrap(ebox, ecluster) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $2 @> $1::ecluster'; + +CREATE OPERATOR <@ ( + leftarg = ebox, + rightarg = ecluster, + procedure = ecluster_ebox_contains_castwrap, + commutator = @>, + restrict = areasel, + join = areajoinsel +); + +CREATE OPERATOR <-> ( + leftarg = epoint, + rightarg = epoint, + procedure = epoint_distance_proc, + commutator = <-> +); + +CREATE OPERATOR <-> ( + leftarg = epoint, + rightarg = ecircle, + procedure = epoint_ecircle_distance_proc, + commutator = <-> +); + +CREATE FUNCTION epoint_ecircle_distance_commutator(ecircle, epoint) + RETURNS float8 + LANGUAGE sql IMMUTABLE AS 'SELECT $2 <-> $1'; + +CREATE OPERATOR <-> ( + leftarg = ecircle, + rightarg = epoint, + procedure = epoint_ecircle_distance_commutator, + commutator = <-> +); + +CREATE OPERATOR <-> ( + leftarg = epoint, + rightarg = ecluster, + procedure = epoint_ecluster_distance_proc, + commutator = <-> +); + +CREATE FUNCTION epoint_ecluster_distance_commutator(ecluster, epoint) + RETURNS float8 + LANGUAGE sql IMMUTABLE AS 'SELECT $2 <-> $1'; + +CREATE OPERATOR <-> ( + leftarg = ecluster, + rightarg = epoint, + procedure = epoint_ecluster_distance_commutator, + commutator = <-> +); + +CREATE OPERATOR <-> ( + leftarg = ecircle, + rightarg = ecircle, + procedure = ecircle_distance_proc, + commutator = <-> +); + +CREATE OPERATOR <-> ( + leftarg = ecircle, + rightarg = ecluster, + procedure = ecircle_ecluster_distance_proc, + commutator = <-> +); + +CREATE FUNCTION ecircle_ecluster_distance_commutator(ecluster, ecircle) + RETURNS float8 + LANGUAGE sql IMMUTABLE AS 'SELECT $2 <-> $1'; + +CREATE OPERATOR <-> ( + leftarg = ecluster, + rightarg = ecircle, + procedure = ecircle_ecluster_distance_commutator, + commutator = <-> +); + +CREATE OPERATOR <-> ( + leftarg = ecluster, + rightarg = ecluster, + procedure = ecluster_distance_proc, + commutator = <-> +); + +CREATE FUNCTION epoint_ebox_distance_castwrap(epoint, ebox) + RETURNS float8 + LANGUAGE sql IMMUTABLE AS 'SELECT $1 <-> $2::ecluster'; + +CREATE OPERATOR <-> ( + leftarg = epoint, + rightarg = ebox, + procedure = epoint_ebox_distance_castwrap, + commutator = <-> +); + +CREATE FUNCTION epoint_ebox_distance_castwrap(ebox, epoint) + RETURNS float8 + LANGUAGE sql IMMUTABLE AS 'SELECT $1::ecluster <-> $2'; + +CREATE OPERATOR <-> ( + leftarg = ebox, + rightarg = epoint, + procedure = epoint_ebox_distance_castwrap, + commutator = <-> +); + +CREATE FUNCTION ebox_distance_castwrap(ebox, ebox) + RETURNS float8 + LANGUAGE sql IMMUTABLE AS 'SELECT $1::ecluster <-> $2::ecluster'; + +CREATE OPERATOR <-> ( + leftarg = ebox, + rightarg = ebox, + procedure = ebox_distance_castwrap, + commutator = <-> +); + +CREATE FUNCTION ebox_ecircle_distance_castwrap(ebox, ecircle) + RETURNS float8 + LANGUAGE sql IMMUTABLE AS 'SELECT $1::ecluster <-> $2'; + +CREATE OPERATOR <-> ( + leftarg = ebox, + rightarg = ecircle, + procedure = ebox_ecircle_distance_castwrap, + commutator = <-> +); + +CREATE FUNCTION ebox_ecircle_distance_castwrap(ecircle, ebox) + RETURNS float8 + LANGUAGE sql IMMUTABLE AS 'SELECT $1 <-> $2::ecluster'; + +CREATE OPERATOR <-> ( + leftarg = ecircle, + rightarg = ebox, + procedure = ebox_ecircle_distance_castwrap, + commutator = <-> +); + +CREATE FUNCTION ebox_ecluster_distance_castwrap(ebox, ecluster) + RETURNS float8 + LANGUAGE sql IMMUTABLE AS 'SELECT $1::ecluster <-> $2'; + +CREATE OPERATOR <-> ( + leftarg = ebox, + rightarg = ecluster, + procedure = ebox_ecluster_distance_castwrap, + commutator = <-> +); + +CREATE FUNCTION ebox_ecluster_distance_castwrap(ecluster, ebox) + RETURNS float8 + LANGUAGE sql IMMUTABLE AS 'SELECT $1 <-> $2::ecluster'; + +CREATE OPERATOR <-> ( + leftarg = ecluster, + rightarg = ebox, + procedure = ebox_ecluster_distance_castwrap, + commutator = <-> +); + + +--------------------- +-- other functions -- +--------------------- + +CREATE FUNCTION monte_carlo_area(ecluster, int4 = 10000) + RETURNS float8 + LANGUAGE C STRICT + AS '$libdir/latlon-v0008', 'pgl_ecluster_monte_carlo_area'; + +CREATE FUNCTION fair_distance(ecluster, epoint, int4 = 10000) + RETURNS float8 + LANGUAGE C STRICT + AS '$libdir/latlon-v0008', 'pgl_ecluster_epoint_fair_distance'; + + +---------------- +-- GiST index -- +---------------- + +CREATE FUNCTION pgl_gist_consistent(internal, internal, smallint, oid, internal) + RETURNS boolean + LANGUAGE C STRICT + AS '$libdir/latlon-v0008', 'pgl_gist_consistent'; + +CREATE FUNCTION pgl_gist_union(internal, internal) + RETURNS internal + LANGUAGE C STRICT + AS '$libdir/latlon-v0008', 'pgl_gist_union'; + +CREATE FUNCTION pgl_gist_compress_epoint(internal) + RETURNS internal + LANGUAGE C STRICT + AS '$libdir/latlon-v0008', 'pgl_gist_compress_epoint'; + +CREATE FUNCTION pgl_gist_compress_ecircle(internal) + RETURNS internal + LANGUAGE C STRICT + AS '$libdir/latlon-v0008', 'pgl_gist_compress_ecircle'; + +CREATE FUNCTION pgl_gist_compress_ecluster(internal) + RETURNS internal + LANGUAGE C STRICT + AS '$libdir/latlon-v0008', 'pgl_gist_compress_ecluster'; + +CREATE FUNCTION pgl_gist_decompress(internal) + RETURNS internal + LANGUAGE C STRICT + AS '$libdir/latlon-v0008', 'pgl_gist_decompress'; + +CREATE FUNCTION pgl_gist_penalty(internal, internal, internal) + RETURNS internal + LANGUAGE C STRICT + AS '$libdir/latlon-v0008', 'pgl_gist_penalty'; + +CREATE FUNCTION pgl_gist_picksplit(internal, internal) + RETURNS internal + LANGUAGE C STRICT + AS '$libdir/latlon-v0008', 'pgl_gist_picksplit'; + +CREATE FUNCTION pgl_gist_same(internal, internal, internal) + RETURNS internal + LANGUAGE C STRICT + AS '$libdir/latlon-v0008', 'pgl_gist_same'; + +CREATE FUNCTION pgl_gist_distance(internal, internal, smallint, oid) + RETURNS internal + LANGUAGE C STRICT + AS '$libdir/latlon-v0008', 'pgl_gist_distance'; + +CREATE OPERATOR CLASS epoint_ops + DEFAULT FOR TYPE epoint USING gist AS + OPERATOR 11 = , + OPERATOR 22 && (epoint, ebox), + OPERATOR 222 <@ (epoint, ebox), + OPERATOR 23 && (epoint, ecircle), + OPERATOR 24 && (epoint, ecluster), + OPERATOR 124 &&+ (epoint, ecluster), + OPERATOR 224 <@ (epoint, ecluster), + OPERATOR 31 <-> (epoint, epoint) FOR ORDER BY float_ops, + OPERATOR 32 <-> (epoint, ebox) FOR ORDER BY float_ops, + OPERATOR 33 <-> (epoint, ecircle) FOR ORDER BY float_ops, + OPERATOR 34 <-> (epoint, ecluster) FOR ORDER BY float_ops, + FUNCTION 1 pgl_gist_consistent(internal, internal, smallint, oid, internal), + FUNCTION 2 pgl_gist_union(internal, internal), + FUNCTION 3 pgl_gist_compress_epoint(internal), + FUNCTION 4 pgl_gist_decompress(internal), + FUNCTION 5 pgl_gist_penalty(internal, internal, internal), + FUNCTION 6 pgl_gist_picksplit(internal, internal), + FUNCTION 7 pgl_gist_same(internal, internal, internal), + FUNCTION 8 pgl_gist_distance(internal, internal, smallint, oid), + STORAGE ekey_point; + +CREATE OPERATOR CLASS ecircle_ops + DEFAULT FOR TYPE ecircle USING gist AS + OPERATOR 13 = , + OPERATOR 21 && (ecircle, epoint), + OPERATOR 22 && (ecircle, ebox), + OPERATOR 122 &&+ (ecircle, ebox), + OPERATOR 23 && (ecircle, ecircle), + OPERATOR 24 && (ecircle, ecluster), + OPERATOR 124 &&+ (ecircle, ecluster), + OPERATOR 31 <-> (ecircle, epoint) FOR ORDER BY float_ops, + OPERATOR 32 <-> (ecircle, ebox) FOR ORDER BY float_ops, + OPERATOR 33 <-> (ecircle, ecircle) FOR ORDER BY float_ops, + OPERATOR 34 <-> (ecircle, ecluster) FOR ORDER BY float_ops, + FUNCTION 1 pgl_gist_consistent(internal, internal, smallint, oid, internal), + FUNCTION 2 pgl_gist_union(internal, internal), + FUNCTION 3 pgl_gist_compress_ecircle(internal), + FUNCTION 4 pgl_gist_decompress(internal), + FUNCTION 5 pgl_gist_penalty(internal, internal, internal), + FUNCTION 6 pgl_gist_picksplit(internal, internal), + FUNCTION 7 pgl_gist_same(internal, internal, internal), + FUNCTION 8 pgl_gist_distance(internal, internal, smallint, oid), + STORAGE ekey_area; + +CREATE OPERATOR CLASS ecluster_ops + DEFAULT FOR TYPE ecluster USING gist AS + OPERATOR 21 && (ecluster, epoint), + OPERATOR 121 &&+ (ecluster, epoint), + OPERATOR 221 @> (ecluster, epoint), + OPERATOR 22 && (ecluster, ebox), + OPERATOR 122 &&+ (ecluster, ebox), + OPERATOR 222 @> (ecluster, ebox), + OPERATOR 322 <@ (ecluster, ebox), + OPERATOR 23 && (ecluster, ecircle), + OPERATOR 123 &&+ (ecluster, ecircle), + OPERATOR 24 && (ecluster, ecluster), + OPERATOR 124 &&+ (ecluster, ecluster), + OPERATOR 224 @> (ecluster, ecluster), + OPERATOR 324 <@ (ecluster, ecluster), + OPERATOR 31 <-> (ecluster, epoint) FOR ORDER BY float_ops, + OPERATOR 32 <-> (ecluster, ebox) FOR ORDER BY float_ops, + OPERATOR 33 <-> (ecluster, ecircle) FOR ORDER BY float_ops, + OPERATOR 34 <-> (ecluster, ecluster) FOR ORDER BY float_ops, + FUNCTION 1 pgl_gist_consistent(internal, internal, smallint, oid, internal), + FUNCTION 2 pgl_gist_union(internal, internal), + FUNCTION 3 pgl_gist_compress_ecluster(internal), + FUNCTION 4 pgl_gist_decompress(internal), + FUNCTION 5 pgl_gist_penalty(internal, internal, internal), + FUNCTION 6 pgl_gist_picksplit(internal, internal), + FUNCTION 7 pgl_gist_same(internal, internal, internal), + FUNCTION 8 pgl_gist_distance(internal, internal, smallint, oid), + STORAGE ekey_area; + + +--------------------- +-- alias functions -- +--------------------- + +CREATE FUNCTION distance(epoint, epoint) + RETURNS float8 + LANGUAGE sql IMMUTABLE AS 'SELECT $1 <-> $2'; + +CREATE FUNCTION distance(ecluster, epoint) + RETURNS float8 + LANGUAGE sql IMMUTABLE AS 'SELECT $1 <-> $2'; + +CREATE FUNCTION distance_within(epoint, epoint, float8) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $1 && ecircle($2, $3)'; + +CREATE FUNCTION distance_within(ecluster, epoint, float8) + RETURNS boolean + LANGUAGE sql IMMUTABLE AS 'SELECT $1 && ecircle($2, $3)'; + + +-------------------------------- +-- other data storage formats -- +-------------------------------- + +CREATE FUNCTION coords_to_epoint(float8, float8, text = 'epoint') + RETURNS epoint + LANGUAGE plpgsql IMMUTABLE STRICT AS $$ + DECLARE + "result" epoint; + BEGIN + IF $3 = 'epoint_lonlat' THEN + -- avoid dynamic command execution for better performance + RETURN epoint($2, $1); + END IF; + IF $3 = 'epoint' OR $3 = 'epoint_latlon' THEN + -- avoid dynamic command execution for better performance + RETURN epoint($1, $2); + END IF; + EXECUTE 'SELECT ' || $3 || '($1, $2)' INTO STRICT "result" USING $1, $2; + RETURN "result"; + END; + $$; + +CREATE FUNCTION GeoJSON_LinearRing_vertices(jsonb, text = 'epoint_lonlat') + RETURNS SETOF jsonb + LANGUAGE sql IMMUTABLE STRICT AS $$ + SELECT "result" FROM + ( SELECT jsonb_array_length($1) - 1 ) AS "lastindex_row" ("lastindex") + CROSS JOIN LATERAL jsonb_array_elements( + CASE WHEN + coords_to_epoint( + ($1->0->>0)::float8, + ($1->0->>1)::float8, + $2 + ) = coords_to_epoint( + ($1->"lastindex"->>0)::float8, + ($1->"lastindex"->>1)::float8, + $2 + ) + THEN + $1 - "lastindex" + ELSE + $1 + END + ) AS "result_row" ("result") + $$; + +CREATE FUNCTION GeoJSON_to_epoint(jsonb, text = 'epoint_lonlat') + RETURNS epoint + LANGUAGE sql IMMUTABLE STRICT AS $$ + SELECT CASE + WHEN $1->>'type' = 'Point' THEN + coords_to_epoint( + ($1->'coordinates'->>0)::float8, + ($1->'coordinates'->>1)::float8, + $2 + ) + WHEN $1->>'type' = 'Feature' THEN + GeoJSON_to_epoint($1->'geometry', $2) + ELSE + NULL + END + $$; + +CREATE FUNCTION GeoJSON_to_ecluster(jsonb, text = 'epoint_lonlat') + RETURNS ecluster + LANGUAGE sql IMMUTABLE STRICT AS $$ + SELECT CASE $1->>'type' + WHEN 'Point' THEN + coords_to_epoint( + ($1->'coordinates'->>0)::float8, + ($1->'coordinates'->>1)::float8, + $2 + )::ecluster + WHEN 'MultiPoint' THEN + ( SELECT ecluster_create_multipoint(array_agg( + coords_to_epoint( + ("coord"->>0)::float8, + ("coord"->>1)::float8, + $2 + ) + )) + FROM jsonb_array_elements($1->'coordinates') AS "coord" + ) + WHEN 'LineString' THEN + ( SELECT ecluster_create_path(array_agg( + coords_to_epoint( + ("coord"->>0)::float8, + ("coord"->>1)::float8, + $2 + ) + )) + FROM jsonb_array_elements($1->'coordinates') AS "coord" + ) + WHEN 'MultiLineString' THEN + ( SELECT ecluster_concat(array_agg( + ( SELECT ecluster_create_path(array_agg( + coords_to_epoint( + ("coord"->>0)::float8, + ("coord"->>1)::float8, + $2 + ) + )) + FROM jsonb_array_elements("coord_array") AS "coord" + ) + )) + FROM jsonb_array_elements($1->'coordinates') AS "coord_array" + ) + WHEN 'Polygon' THEN + ( SELECT ecluster_concat(array_agg( + ( SELECT ecluster_create_polygon(array_agg( + coords_to_epoint( + ("coord"->>0)::float8, + ("coord"->>1)::float8, + $2 + ) + )) + FROM GeoJSON_LinearRing_vertices("coord_array", $2) AS "coord" + ) + )) + FROM jsonb_array_elements($1->'coordinates') AS "coord_array" + ) + WHEN 'MultiPolygon' THEN + ( SELECT ecluster_concat(array_agg( + ( SELECT ecluster_concat(array_agg( + ( SELECT ecluster_create_polygon(array_agg( + coords_to_epoint( + ("coord"->>0)::float8, + ("coord"->>1)::float8, + $2 + ) + )) + FROM GeoJSON_LinearRing_vertices("coord_array", $2) AS "coord" + ) + )) + FROM jsonb_array_elements("coord_array_array") AS "coord_array" + ) + )) + FROM jsonb_array_elements($1->'coordinates') AS "coord_array_array" + ) + WHEN 'GeometryCollection' THEN + ( SELECT ecluster_concat(array_agg( + GeoJSON_to_ecluster("geometry", $2) + )) + FROM jsonb_array_elements($1->'geometries') AS "geometry" + ) + WHEN 'Feature' THEN + GeoJSON_to_ecluster($1->'geometry', $2) + WHEN 'FeatureCollection' THEN + ( SELECT ecluster_concat(array_agg( + GeoJSON_to_ecluster("feature", $2) + )) + FROM jsonb_array_elements($1->'features') AS "feature" + ) + ELSE + NULL + END + $$; + diff -r a90c0d750405 -r 1b9cd45e9e48 latlon-v0008.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/latlon-v0008.c Tue Oct 25 18:44:43 2016 +0200 @@ -0,0 +1,3285 @@ + +/*-------------* + * C prelude * + *-------------*/ + +#include "postgres.h" +#include "fmgr.h" +#include "libpq/pqformat.h" +#include "access/gist.h" +#include "access/stratnum.h" +#include "utils/array.h" +#include + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +#if INT_MAX < 2147483647 +#error Expected int type to be at least 32 bit wide +#endif + + +/*---------------------------------* + * distance calculation on earth * + * (using WGS-84 spheroid) * + *---------------------------------*/ + +/* WGS-84 spheroid with following parameters: + semi-major axis a = 6378137 + semi-minor axis b = a * (1 - 1/298.257223563) + estimated diameter = 2 * (2*a+b)/3 +*/ +#define PGL_SPHEROID_A 6378137.0 /* semi major axis */ +#define PGL_SPHEROID_F (1.0/298.257223563) /* flattening */ +#define PGL_SPHEROID_B (PGL_SPHEROID_A * (1.0-PGL_SPHEROID_F)) +#define PGL_EPS2 ( ( PGL_SPHEROID_A * PGL_SPHEROID_A - \ + PGL_SPHEROID_B * PGL_SPHEROID_B ) / \ + ( PGL_SPHEROID_A * PGL_SPHEROID_A ) ) +#define PGL_SUBEPS2 (1.0-PGL_EPS2) +#define PGL_RADIUS ((2.0*PGL_SPHEROID_A + PGL_SPHEROID_B) / 3.0) +#define PGL_DIAMETER (2.0 * PGL_RADIUS) +#define PGL_SCALE (PGL_SPHEROID_A / PGL_DIAMETER) /* semi-major ref. */ +#define PGL_MAXDIST (PGL_RADIUS * M_PI) /* maximum distance */ +#define PGL_FADELIMIT (PGL_MAXDIST / 3.0) /* 1/6 circumference */ + +/* calculate distance between two points on earth (given in degrees) */ +static inline double pgl_distance( + double lat1, double lon1, double lat2, double lon2 +) { + float8 lat1cos, lat1sin, lat2cos, lat2sin, lon2cos, lon2sin; + float8 nphi1, nphi2, x1, z1, x2, y2, z2, g, s, t; + /* normalize delta longitude (lon2 > 0 && lon1 = 0) */ + /* lon1 = 0 (not used anymore) */ + lon2 = fabs(lon2-lon1); + /* convert to radians (first divide, then multiply) */ + lat1 = (lat1 / 180.0) * M_PI; + lat2 = (lat2 / 180.0) * M_PI; + lon2 = (lon2 / 180.0) * M_PI; + /* make lat2 >= lat1 to ensure reversal-symmetry despite floating point + operations (lon2 >= lon1 is already ensured in a previous step) */ + if (lat2 < lat1) { float8 swap = lat1; lat1 = lat2; lat2 = swap; } + /* calculate 3d coordinates on scaled ellipsoid which has an average diameter + of 1.0 */ + lat1cos = cos(lat1); lat1sin = sin(lat1); + lat2cos = cos(lat2); lat2sin = sin(lat2); + lon2cos = cos(lon2); lon2sin = sin(lon2); + nphi1 = PGL_SCALE / sqrt(1 - PGL_EPS2 * lat1sin * lat1sin); + nphi2 = PGL_SCALE / sqrt(1 - PGL_EPS2 * lat2sin * lat2sin); + x1 = nphi1 * lat1cos; + z1 = nphi1 * PGL_SUBEPS2 * lat1sin; + x2 = nphi2 * lat2cos * lon2cos; + y2 = nphi2 * lat2cos * lon2sin; + z2 = nphi2 * PGL_SUBEPS2 * lat2sin; + /* calculate tunnel distance through scaled (diameter 1.0) ellipsoid */ + g = sqrt((x2-x1)*(x2-x1) + y2*y2 + (z2-z1)*(z2-z1)); + /* convert tunnel distance through scaled ellipsoid to approximated surface + distance on original ellipsoid */ + if (g > 1.0) g = 1.0; + s = PGL_DIAMETER * asin(g); + /* return result only if small enough to be precise (less than 1/3 of + maximum possible distance) */ + if (s <= PGL_FADELIMIT) return s; + /* calculate tunnel distance to antipodal point through scaled ellipsoid */ + g = sqrt((x2+x1)*(x2+x1) + y2*y2 + (z2+z1)*(z2+z1)); + /* convert tunnel distance to antipodal point through scaled ellipsoid to + approximated surface distance to antipodal point on original ellipsoid */ + if (g > 1.0) g = 1.0; + t = PGL_DIAMETER * asin(g); + /* surface distance between original points can now be approximated by + substracting antipodal distance from maximum possible distance; + return result only if small enough (less than 1/3 of maximum possible + distance) */ + if (t <= PGL_FADELIMIT) return PGL_MAXDIST-t; + /* otherwise crossfade direct and antipodal result to ensure monotonicity */ + return ( + (s * (t-PGL_FADELIMIT) + (PGL_MAXDIST-t) * (s-PGL_FADELIMIT)) / + (s + t - 2*PGL_FADELIMIT) + ); +} + +/* finite distance that can not be reached on earth */ +#define PGL_ULTRA_DISTANCE (3 * PGL_MAXDIST) + + +/*--------------------------------* + * simple geographic data types * + *--------------------------------*/ + +/* point on earth given by latitude and longitude in degrees */ +/* (type "epoint" in SQL) */ +typedef struct { + double lat; /* between -90 and 90 (both inclusive) */ + double lon; /* between -180 and 180 (both inclusive) */ +} pgl_point; + +/* box delimited by two parallels and two meridians (all in degrees) */ +/* (type "ebox" in SQL) */ +typedef struct { + double lat_min; /* between -90 and 90 (both inclusive) */ + double lat_max; /* between -90 and 90 (both inclusive) */ + double lon_min; /* between -180 and 180 (both inclusive) */ + double lon_max; /* between -180 and 180 (both inclusive) */ + /* if lat_min > lat_max, then box is empty */ + /* if lon_min > lon_max, then 180th meridian is crossed */ +} pgl_box; + +/* circle on earth surface (for radial searches with fixed radius) */ +/* (type "ecircle" in SQL) */ +typedef struct { + pgl_point center; + double radius; /* positive (including +0 but excluding -0), or -INFINITY */ + /* A negative radius (i.e. -INFINITY) denotes nothing (i.e. no point), + zero radius (0) denotes a single point, + a finite radius (0 < radius < INFINITY) denotes a filled circle, and + a radius of INFINITY is valid and means complete coverage of earth. */ +} pgl_circle; + + +/*----------------------------------* + * geographic "cluster" data type * + *----------------------------------*/ + +/* A cluster is a collection of points, paths, outlines, and polygons. If two + polygons in a cluster overlap, the area covered by both polygons does not + belong to the cluster. This way, a cluster can be used to describe complex + shapes like polygons with holes. Outlines are non-filled polygons. Paths are + open by default (i.e. the last point in the list is not connected with the + first point in the list). Note that each outline or polygon in a cluster + must cover a longitude range of less than 180 degrees to avoid ambiguities. + Areas which are larger may be split into multiple polygons. */ + +/* maximum number of points in a cluster */ +/* (limited to avoid integer overflows, e.g. when allocating memory) */ +#define PGL_CLUSTER_MAXPOINTS 16777216 + +/* types of cluster entries */ +#define PGL_ENTRY_POINT 1 /* a point */ +#define PGL_ENTRY_PATH 2 /* a path from first point to last point */ +#define PGL_ENTRY_OUTLINE 3 /* a non-filled polygon with given vertices */ +#define PGL_ENTRY_POLYGON 4 /* a filled polygon with given vertices */ + +/* Entries of a cluster are described by two different structs: pgl_newentry + and pgl_entry. The first is used only during construction of a cluster, the + second is used in all other cases (e.g. when reading clusters from the + database, performing operations, etc). */ + +/* entry for new geographic cluster during construction of that cluster */ +typedef struct { + int32_t entrytype; + int32_t npoints; + pgl_point *points; /* pointer to an array of points (pgl_point) */ +} pgl_newentry; + +/* entry of geographic cluster */ +typedef struct { + int32_t entrytype; /* type of entry: point, path, outline, polygon */ + int32_t npoints; /* number of stored points (set to 1 for point entry) */ + int32_t offset; /* offset of pgl_point array from cluster base address */ + /* use macro PGL_ENTRY_POINTS to obtain a pointer to the array of points */ +} pgl_entry; + +/* geographic cluster which is a collection of points, (open) paths, polygons, + and outlines (non-filled polygons) */ +typedef struct { + char header[VARHDRSZ]; /* PostgreSQL header for variable size data types */ + int32_t nentries; /* number of stored points */ + pgl_circle bounding; /* bounding circle */ + /* Note: bounding circle ensures alignment of pgl_cluster for points */ + pgl_entry entries[FLEXIBLE_ARRAY_MEMBER]; /* var-length data */ +} pgl_cluster; + +/* macro to determine memory alignment of points */ +/* (needed to store pgl_point array after entries in pgl_cluster) */ +typedef struct { char dummy; pgl_point aligned; } pgl_point_alignment; +#define PGL_POINT_ALIGNMENT offsetof(pgl_point_alignment, aligned) + +/* macro to extract a pointer to the array of points of a cluster entry */ +#define PGL_ENTRY_POINTS(cluster, idx) \ + ((pgl_point *)(((intptr_t)cluster)+(cluster)->entries[idx].offset)) + +/* convert pgl_newentry array to pgl_cluster */ +/* NOTE: requires pgl_finalize_cluster to be called to finalize result */ +static pgl_cluster *pgl_new_cluster(int nentries, pgl_newentry *entries) { + int i; /* index of current entry */ + int npoints = 0; /* number of points in whole cluster */ + int entry_npoints; /* number of points in current entry */ + int points_offset = PGL_POINT_ALIGNMENT * ( + ( offsetof(pgl_cluster, entries) + + nentries * sizeof(pgl_entry) + + PGL_POINT_ALIGNMENT - 1 + ) / PGL_POINT_ALIGNMENT + ); /* offset of pgl_point array from base address (considering alignment) */ + pgl_cluster *cluster; /* new cluster to be returned */ + /* determine total number of points */ + for (i=0; ientries[i].entrytype = entries[i].entrytype; + cluster->entries[i].npoints = entry_npoints; + /* calculate offset (in bytes) of pgl_point array */ + cluster->entries[i].offset = points_offset + npoints * sizeof(pgl_point); + /* copy points */ + memcpy( + PGL_ENTRY_POINTS(cluster, i), + entries[i].points, + entry_npoints * sizeof(pgl_point) + ); + /* update total number of points processed */ + npoints += entry_npoints; + } + /* set number of entries in cluster */ + cluster->nentries = nentries; + /* set PostgreSQL header for variable sized data */ + SET_VARSIZE(cluster, points_offset + npoints * sizeof(pgl_point)); + /* return newly created cluster */ + return cluster; +} + + +/*----------------------------------------* + * C functions on geographic data types * + *----------------------------------------*/ + +/* round latitude or longitude to 12 digits after decimal point */ +static inline double pgl_round(double val) { + return round(val * 1e12) / 1e12; +} + +/* compare two points */ +/* (equality when same point on earth is described, otherwise an arbitrary + linear order) */ +static int pgl_point_cmp(pgl_point *point1, pgl_point *point2) { + double lon1, lon2; /* modified longitudes for special cases */ + /* use latitude as first ordering criterion */ + if (point1->lat < point2->lat) return -1; + if (point1->lat > point2->lat) return 1; + /* determine modified longitudes (considering special case of poles and + 180th meridian which can be described as W180 or E180) */ + if (point1->lat == -90 || point1->lat == 90) lon1 = 0; + else if (point1->lon == 180) lon1 = -180; + else lon1 = point1->lon; + if (point2->lat == -90 || point2->lat == 90) lon2 = 0; + else if (point2->lon == 180) lon2 = -180; + else lon2 = point2->lon; + /* use (modified) longitude as secondary ordering criterion */ + if (lon1 < lon2) return -1; + if (lon1 > lon2) return 1; + /* no difference found, points are equal */ + return 0; +} + +/* compare two boxes */ +/* (equality when same box on earth is described, otherwise an arbitrary linear + order) */ +static int pgl_box_cmp(pgl_box *box1, pgl_box *box2) { + /* two empty boxes are equal, and an empty box is always considered "less + than" a non-empty box */ + if (box1->lat_min> box1->lat_max && box2->lat_min<=box2->lat_max) return -1; + if (box1->lat_min> box1->lat_max && box2->lat_min> box2->lat_max) return 0; + if (box1->lat_min<=box1->lat_max && box2->lat_min> box2->lat_max) return 1; + /* use southern border as first ordering criterion */ + if (box1->lat_min < box2->lat_min) return -1; + if (box1->lat_min > box2->lat_min) return 1; + /* use northern border as second ordering criterion */ + if (box1->lat_max < box2->lat_max) return -1; + if (box1->lat_max > box2->lat_max) return 1; + /* use western border as third ordering criterion */ + if (box1->lon_min < box2->lon_min) return -1; + if (box1->lon_min > box2->lon_min) return 1; + /* use eastern border as fourth ordering criterion */ + if (box1->lon_max < box2->lon_max) return -1; + if (box1->lon_max > box2->lon_max) return 1; + /* no difference found, boxes are equal */ + return 0; +} + +/* compare two circles */ +/* (equality when same circle on earth is described, otherwise an arbitrary + linear order) */ +static int pgl_circle_cmp(pgl_circle *circle1, pgl_circle *circle2) { + /* two circles with same infinite radius (positive or negative infinity) are + considered equal independently of center point */ + if ( + !isfinite(circle1->radius) && !isfinite(circle2->radius) && + circle1->radius == circle2->radius + ) return 0; + /* use radius as first ordering criterion */ + if (circle1->radius < circle2->radius) return -1; + if (circle1->radius > circle2->radius) return 1; + /* use center point as secondary ordering criterion */ + return pgl_point_cmp(&(circle1->center), &(circle2->center)); +} + +/* set box to empty box*/ +static void pgl_box_set_empty(pgl_box *box) { + box->lat_min = INFINITY; + box->lat_max = -INFINITY; + box->lon_min = 0; + box->lon_max = 0; +} + +/* check if point is inside a box */ +static bool pgl_point_in_box(pgl_point *point, pgl_box *box) { + return ( + point->lat >= box->lat_min && point->lat <= box->lat_max && ( + (box->lon_min > box->lon_max) ? ( + /* box crosses 180th meridian */ + point->lon >= box->lon_min || point->lon <= box->lon_max + ) : ( + /* box does not cross the 180th meridian */ + point->lon >= box->lon_min && point->lon <= box->lon_max + ) + ) + ); +} + +/* check if two boxes overlap */ +static bool pgl_boxes_overlap(pgl_box *box1, pgl_box *box2) { + return ( + box2->lat_max >= box2->lat_min && /* ensure box2 is not empty */ + ( box2->lat_min >= box1->lat_min || box2->lat_max >= box1->lat_min ) && + ( box2->lat_min <= box1->lat_max || box2->lat_max <= box1->lat_max ) && ( + ( + /* check if one and only one box crosses the 180th meridian */ + ((box1->lon_min > box1->lon_max) ? 1 : 0) ^ + ((box2->lon_min > box2->lon_max) ? 1 : 0) + ) ? ( + /* exactly one box crosses the 180th meridian */ + box2->lon_min >= box1->lon_min || box2->lon_max >= box1->lon_min || + box2->lon_min <= box1->lon_max || box2->lon_max <= box1->lon_max + ) : ( + /* no box or both boxes cross the 180th meridian */ + ( + (box2->lon_min >= box1->lon_min || box2->lon_max >= box1->lon_min) && + (box2->lon_min <= box1->lon_max || box2->lon_max <= box1->lon_max) + ) || + /* handle W180 == E180 */ + ( box1->lon_min == -180 && box2->lon_max == 180 ) || + ( box2->lon_min == -180 && box1->lon_max == 180 ) + ) + ) + ); +} + +/* check unambiguousness of east/west orientation of cluster entries and set + bounding circle of cluster */ +static bool pgl_finalize_cluster(pgl_cluster *cluster) { + int i, j; /* i: index of entry, j: index of point in entry */ + int npoints; /* number of points in entry */ + int total_npoints = 0; /* total number of points in cluster */ + pgl_point *points; /* points in entry */ + int lon_dir; /* first point of entry west (-1) or east (+1) */ + double lon_break = 0; /* antipodal longitude of first point in entry */ + double lon_min, lon_max; /* covered longitude range of entry */ + double value; /* temporary variable */ + /* reset bounding circle center to empty circle at 0/0 coordinates */ + cluster->bounding.center.lat = 0; + cluster->bounding.center.lon = 0; + cluster->bounding.radius = -INFINITY; + /* if cluster is not empty */ + if (cluster->nentries != 0) { + /* iterate over all cluster entries and ensure they each cover a longitude + range less than 180 degrees */ + for (i=0; inentries; i++) { + /* get properties of entry */ + npoints = cluster->entries[i].npoints; + points = PGL_ENTRY_POINTS(cluster, i); + /* get longitude of first point of entry */ + value = points[0].lon; + /* initialize lon_min and lon_max with longitude of first point */ + lon_min = value; + lon_max = value; + /* determine east/west orientation of first point and calculate antipodal + longitude (Note: rounding required here) */ + if (value < 0) { lon_dir = -1; lon_break = pgl_round(value + 180); } + else if (value > 0) { lon_dir = 1; lon_break = pgl_round(value - 180); } + else lon_dir = 0; + /* iterate over all other points in entry */ + for (j=1; jlon_break) value = pgl_round(value - 360); + else if (lon_dir>0 && value lon_max) lon_max = value; + /* return false if 180 degrees or more are covered */ + if (lon_max - lon_min >= 180) return false; + } + } + /* iterate over all points of all entries and calculate arbitrary center + point for bounding circle (best if center point minimizes the radius, + but some error is allowed here) */ + for (i=0; inentries; i++) { + /* get properties of entry */ + npoints = cluster->entries[i].npoints; + points = PGL_ENTRY_POINTS(cluster, i); + /* check if first entry */ + if (i==0) { + /* get longitude of first point of first entry in whole cluster */ + value = points[0].lon; + /* initialize lon_min and lon_max with longitude of first point of + first entry in whole cluster (used to determine if whole cluster + covers a longitude range of 180 degrees or more) */ + lon_min = value; + lon_max = value; + /* determine east/west orientation of first point and calculate + antipodal longitude (Note: rounding not necessary here) */ + if (value < 0) { lon_dir = -1; lon_break = value + 180; } + else if (value > 0) { lon_dir = 1; lon_break = value - 180; } + else lon_dir = 0; + } + /* iterate over all points in entry */ + for (j=0; j lon_break) value -= 360; + else if (lon_dir > 0 && value < lon_break) value += 360; + if (value < lon_min) lon_min = value; + else if (value > lon_max) lon_max = value; + /* set bounding circle to cover whole earth if more than 180 degrees + are covered */ + if (lon_max - lon_min >= 180) { + cluster->bounding.center.lat = 0; + cluster->bounding.center.lon = 0; + cluster->bounding.radius = INFINITY; + return true; + } + /* add point to bounding circle center (for average calculation) */ + cluster->bounding.center.lat += points[j].lat; + cluster->bounding.center.lon += value; + } + /* count total number of points */ + total_npoints += npoints; + } + /* determine average latitude and longitude of cluster */ + cluster->bounding.center.lat /= total_npoints; + cluster->bounding.center.lon /= total_npoints; + /* normalize longitude of center of cluster bounding circle */ + if (cluster->bounding.center.lon < -180) { + cluster->bounding.center.lon += 360; + } + else if (cluster->bounding.center.lon > 180) { + cluster->bounding.center.lon -= 360; + } + /* round bounding circle center (useful if it is used by other functions) */ + cluster->bounding.center.lat = pgl_round(cluster->bounding.center.lat); + cluster->bounding.center.lon = pgl_round(cluster->bounding.center.lon); + /* calculate radius of bounding circle */ + for (i=0; inentries; i++) { + npoints = cluster->entries[i].npoints; + points = PGL_ENTRY_POINTS(cluster, i); + for (j=0; jbounding.center.lat, cluster->bounding.center.lon, + points[j].lat, points[j].lon + ); + if (value > cluster->bounding.radius) cluster->bounding.radius = value; + } + } + } + /* return true (east/west orientation is unambiguous) */ + return true; +} + +/* check if point is inside cluster */ +/* (if point is on perimeter, then true is returned if and only if + strict == false) */ +static bool pgl_point_in_cluster( + pgl_point *point, + pgl_cluster *cluster, + bool strict +) { + int i, j, k; /* i: entry, j: point in entry, k: next point in entry */ + int entrytype; /* type of entry */ + int npoints; /* number of points in entry */ + pgl_point *points; /* array of points in entry */ + int lon_dir = 0; /* first vertex west (-1) or east (+1) */ + double lon_break = 0; /* antipodal longitude of first vertex */ + double lat0 = point->lat; /* latitude of point */ + double lon0; /* (adjusted) longitude of point */ + double lat1, lon1; /* latitude and (adjusted) longitude of vertex */ + double lat2, lon2; /* latitude and (adjusted) longitude of next vertex */ + double lon; /* longitude of intersection */ + int counter = 0; /* counter for intersections east of point */ + /* iterate over all entries */ + for (i=0; inentries; i++) { + /* get type of entry */ + entrytype = cluster->entries[i].entrytype; + /* skip all entries but polygons if perimeters are excluded */ + if (strict && entrytype != PGL_ENTRY_POLYGON) continue; + /* get points of entry */ + npoints = cluster->entries[i].npoints; + points = PGL_ENTRY_POINTS(cluster, i); + /* determine east/west orientation of first point of entry and calculate + antipodal longitude */ + lon_break = points[0].lon; + if (lon_break < 0) { lon_dir = -1; lon_break += 180; } + else if (lon_break > 0) { lon_dir = 1; lon_break -= 180; } + else lon_dir = 0; + /* get longitude of point */ + lon0 = point->lon; + /* consider longitude wrap-around for point */ + if (lon_dir < 0 && lon0 > lon_break) lon0 = pgl_round(lon0 - 360); + else if (lon_dir > 0 && lon0 < lon_break) lon0 = pgl_round(lon0 + 360); + /* iterate over all edges and vertices */ + for (j=0; j lon_break) lon1 = pgl_round(lon1 - 360); + else if (lon_dir > 0 && lon1 < lon_break) lon1 = pgl_round(lon1 + 360); + } + /* get latitude and longitude of next vertex */ + lat2 = points[k].lat; + lon2 = points[k].lon; + /* consider longitude wrap-around for next vertex */ + if (lon_dir < 0 && lon2 > lon_break) lon2 = pgl_round(lon2 - 360); + else if (lon_dir > 0 && lon2 < lon_break) lon2 = pgl_round(lon2 + 360); + /* return if point is on horizontal (west to east) edge of polygon */ + if ( + lat0 == lat1 && lat0 == lat2 && + ( (lon0 >= lon1 && lon0 <= lon2) || (lon0 >= lon2 && lon0 <= lon1) ) + ) return !strict; + /* check if edge crosses east/west line of point */ + if ((lat1 < lat0 && lat2 >= lat0) || (lat2 < lat0 && lat1 >= lat0)) { + /* calculate longitude of intersection */ + lon = (lon1 * (lat2-lat0) + lon2 * (lat0-lat1)) / (lat2-lat1); + /* return if intersection goes (approximately) through point */ + if (pgl_round(lon) == lon0) return !strict; + /* count intersection if east of point and entry is polygon*/ + if (entrytype == PGL_ENTRY_POLYGON && lon > lon0) counter++; + } + } + } + /* return true if number of intersections is odd */ + return counter & 1; +} + +/* check if all points of the second cluster are strictly inside the first + cluster */ +static inline bool pgl_all_cluster_points_strictly_in_cluster( + pgl_cluster *outer, pgl_cluster *inner +) { + int i, j; /* i: entry, j: point in entry */ + int npoints; /* number of points in entry */ + pgl_point *points; /* array of points in entry */ + /* iterate over all entries of "inner" cluster */ + for (i=0; inentries; i++) { + /* get properties of entry */ + npoints = inner->entries[i].npoints; + points = PGL_ENTRY_POINTS(inner, i); + /* iterate over all points in entry of "inner" cluster */ + for (j=0; jnentries; i++) { + /* get properties of entry */ + npoints = inner->entries[i].npoints; + points = PGL_ENTRY_POINTS(inner, i); + /* iterate over all points in entry of "inner" cluster */ + for (j=0; jnentries; i1++) { + /* get properties of entry in cluster1 and skip points */ + npoints1 = cluster1->entries[i1].npoints; + if (npoints1 < 2) continue; + entrytype1 = cluster1->entries[i1].entrytype; + points1 = PGL_ENTRY_POINTS(cluster1, i1); + /* determine east/west orientation of first point and calculate antipodal + longitude */ + lon_break1 = points1[0].lon; + if (lon_break1 < 0) { + lon_dir1 = -1; + lon_break1 = pgl_round(lon_break1 + 180); + } else if (lon_break1 > 0) { + lon_dir1 = 1; + lon_break1 = pgl_round(lon_break1 - 180); + } else lon_dir1 = 0; + /* iterate over all edges and vertices in cluster1 */ + for (j1=0; j1lon_break1) lon11 = pgl_round(lon11-360); + else if (lon_dir1>0 && lon11lon_break1) lon12 = pgl_round(lon12-360); + else if (lon_dir1>0 && lon12nentries; i2++) { + /* get points and number of points of entry in cluster2 */ + npoints2 = cluster2->entries[i2].npoints; + if (npoints2 < 2) continue; + entrytype2 = cluster2->entries[i2].entrytype; + points2 = PGL_ENTRY_POINTS(cluster2, i2); + /* determine east/west orientation of first point and calculate antipodal + longitude */ + lon_break2 = points2[0].lon; + if (lon_break2 < 0) { + lon_dir2 = -1; + lon_break2 = pgl_round(lon_break2 + 180); + } else if (lon_break2 > 0) { + lon_dir2 = 1; + lon_break2 = pgl_round(lon_break2 - 180); + } else lon_dir2 = 0; + /* iterate over all edges and vertices in cluster2 */ + for (j2=0; j2lon_break2) lon21 = pgl_round(lon21-360); + else if (lon_dir2>0 && lon21lon_break2) lon22 = pgl_round(lon22-360); + else if (lon_dir2>0 && lon22 360) { + lon21 = pgl_round(lon21 - 360); + lon22 = pgl_round(lon22 - 360); + } else if (wrapvalue < -360) { + lon21 = pgl_round(lon21 + 360); + lon22 = pgl_round(lon22 + 360); + } + /* return true if segments overlap */ + if ( + pgl_lseg_crosses_line( + lat11, lon11, lat12, lon12, + lat21, lon21, lat22, lon22 + ) && pgl_lseg_crosses_line( + lat21, lon21, lat22, lon22, + lat11, lon11, lat12, lon12 + ) + ) { + return true; + } + } + } + } + } + /* otherwise return false */ + return false; +} + +/* check if second cluster is completely contained in first cluster */ +static bool pgl_cluster_in_cluster(pgl_cluster *outer, pgl_cluster *inner) { + if (!pgl_all_cluster_points_strictly_in_cluster(outer, inner)) return false; + if (pgl_any_cluster_points_in_cluster(inner, outer)) return false; + if (pgl_outlines_overlap(outer, inner)) return false; + return true; +} + +/* check if two clusters overlap */ +static bool pgl_clusters_overlap( + pgl_cluster *cluster1, pgl_cluster *cluster2 +) { + if (pgl_any_cluster_points_in_cluster(cluster1, cluster2)) return true; + if (pgl_any_cluster_points_in_cluster(cluster2, cluster1)) return true; + if (pgl_outlines_overlap(cluster1, cluster2)) return true; + return false; +} + + +/* calculate (approximate) distance between point and cluster */ +static double pgl_point_cluster_distance(pgl_point *point, pgl_cluster *cluster) { + double comp; /* square of compression of meridians */ + int i, j, k; /* i: entry, j: point in entry, k: next point in entry */ + int entrytype; /* type of entry */ + int npoints; /* number of points in entry */ + pgl_point *points; /* array of points in entry */ + int lon_dir = 0; /* first vertex west (-1) or east (+1) */ + double lon_break = 0; /* antipodal longitude of first vertex */ + double lon_min = 0; /* minimum (adjusted) longitude of entry vertices */ + double lon_max = 0; /* maximum (adjusted) longitude of entry vertices */ + double lat0 = point->lat; /* latitude of point */ + double lon0; /* (adjusted) longitude of point */ + double lat1, lon1; /* latitude and (adjusted) longitude of vertex */ + double lat2, lon2; /* latitude and (adjusted) longitude of next vertex */ + double s; /* scalar for vector calculations */ + double dist; /* distance calculated in one step */ + double min_dist = INFINITY; /* minimum distance */ + /* distance is zero if point is contained in cluster */ + if (pgl_point_in_cluster(point, cluster, false)) return 0; + /* calculate approximate square compression of meridians */ + comp = cos((lat0 / 180.0) * M_PI); + comp *= comp; + /* calculate exact square compression of meridians */ + comp *= ( + (1.0 - PGL_EPS2 * (1.0-comp)) * + (1.0 - PGL_EPS2 * (1.0-comp)) / + (PGL_SUBEPS2 * PGL_SUBEPS2) + ); + /* iterate over all entries */ + for (i=0; inentries; i++) { + /* get properties of entry */ + entrytype = cluster->entries[i].entrytype; + npoints = cluster->entries[i].npoints; + points = PGL_ENTRY_POINTS(cluster, i); + /* determine east/west orientation of first point of entry and calculate + antipodal longitude */ + lon_break = points[0].lon; + if (lon_break < 0) { lon_dir = -1; lon_break += 180; } + else if (lon_break > 0) { lon_dir = 1; lon_break -= 180; } + else lon_dir = 0; + /* determine covered longitude range */ + for (j=0; j lon_break) lon1 -= 360; + else if (lon_dir > 0 && lon1 < lon_break) lon1 += 360; + /* update minimum and maximum longitude of polygon */ + if (j == 0 || lon1 < lon_min) lon_min = lon1; + if (j == 0 || lon1 > lon_max) lon_max = lon1; + } + /* adjust longitude wrap-around according to full longitude range */ + lon_break = (lon_max + lon_min) / 2; + if (lon_break < 0) { lon_dir = -1; lon_break += 180; } + else if (lon_break > 0) { lon_dir = 1; lon_break -= 180; } + /* get longitude of point */ + lon0 = point->lon; + /* consider longitude wrap-around for point */ + if (lon_dir < 0 && lon0 > lon_break) lon0 -= 360; + else if (lon_dir > 0 && lon0 < lon_break) lon0 += 360; + /* iterate over all edges and vertices */ + for (j=0; j lon_break) lon1 -= 360; + else if (lon_dir > 0 && lon1 < lon_break) lon1 += 360; + } + /* calculate distance to vertex */ + dist = pgl_distance(lat0, lon0, lat1, lon1); + /* store calculated distance if smallest */ + if (dist < min_dist) min_dist = dist; + /* calculate index of next vertex */ + k = (j+1) % npoints; + /* skip last edge unless entry is (closed) outline or polygon */ + if ( + k == 0 && + entrytype != PGL_ENTRY_OUTLINE && + entrytype != PGL_ENTRY_POLYGON + ) continue; + /* get latitude and longitude of next vertex */ + lat2 = points[k].lat; + lon2 = points[k].lon; + /* consider longitude wrap-around for next vertex */ + if (lon_dir < 0 && lon2 > lon_break) lon2 -= 360; + else if (lon_dir > 0 && lon2 < lon_break) lon2 += 360; + /* go to next vertex and edge if edge is degenerated */ + if (lat1 == lat2 && lon1 == lon2) continue; + /* otherwise test if point can be projected onto edge of polygon */ + s = ( + ((lat0-lat1) * (lat2-lat1) + comp * (lon0-lon1) * (lon2-lon1)) / + ((lat2-lat1) * (lat2-lat1) + comp * (lon2-lon1) * (lon2-lon1)) + ); + /* go to next vertex and edge if point cannot be projected */ + if (!(s > 0 && s < 1)) continue; + /* calculate distance from original point to projected point */ + dist = pgl_distance( + lat0, lon0, + lat1 + s * (lat2-lat1), + lon1 + s * (lon2-lon1) + ); + /* store calculated distance if smallest */ + if (dist < min_dist) min_dist = dist; + } + } + /* return minimum distance */ + return min_dist; +} + +/* calculate (approximate) distance between two clusters */ +static double pgl_cluster_distance(pgl_cluster *cluster1, pgl_cluster *cluster2) { + int i, j; /* i: entry, j: point in entry */ + int npoints; /* number of points in entry */ + pgl_point *points; /* array of points in entry */ + double dist; /* distance calculated in one step */ + double min_dist = INFINITY; /* minimum distance */ + /* consider distance from each point in one cluster to the whole other */ + for (i=0; inentries; i++) { + npoints = cluster1->entries[i].npoints; + points = PGL_ENTRY_POINTS(cluster1, i); + for (j=0; jnentries; i++) { + npoints = cluster2->entries[i].npoints; + points = PGL_ENTRY_POINTS(cluster2, i); + for (j=0; jlat_min > box->lat_max) return INFINITY; + /* return zero if point is inside box */ + if (pgl_point_in_box(point, box)) return 0; + /* calculate delta longitude */ + dlon = box->lon_max - box->lon_min; + if (dlon < 0) dlon += 360; /* 180th meridian crossed */ + /* if delta longitude is greater than 150 degrees, perform safe fall-back */ + if (dlon > 150) return 0; + /* calculate lower limit for distance (formula below requires dlon <= 150) */ + /* TODO: provide better estimation function to improve performance */ + distance = ( + (1.0-1e-14) * pgl_distance( + point->lat, + point->lon, + (box->lat_min + box->lat_max) / 2, + box->lon_min + dlon/2 + ) - pgl_distance( + box->lat_min, box->lon_min, + box->lat_max, box->lon_max + ) + ); + /* truncate negative results to zero */ + if (distance <= 0) distance = 0; + /* return result */ + return distance; +} + + +/*-------------------------------* + * Monte Carlo based functions * + *-------------------------------*/ + +/* half of (spherical) earth's surface area */ +#define PGL_HALF_SURFACE (PGL_RADIUS * PGL_DIAMETER * M_PI) + +/* golden angle in radians */ +#define PGL_GOLDEN_ANGLE (M_PI * (sqrt(5) - 1.0)) + +/* create a list of sample points covering a bounding circle + and return covered area */ +static double pgl_sample_points( + pgl_point *center, /* center of bounding circle */ + double radius, /* radius of bounding circle */ + int samples, /* number of sample points */ + pgl_point *result /* pointer to result array */ +) { + double double_share = 2.0; /* double of covered share of earth's surface */ + double double_share_div_samples; /* double_share divided by sample count */ + int i; + double t; /* parameter of spiral laid on (spherical) earth's surface */ + double x, y, z; /* normalized coordinates of point on non-rotated spiral */ + double sin_phi; /* sine of sph. coordinate of point of non-rotated spiral */ + double lambda; /* other sph. coordinate of point of non-rotated spiral */ + double rot = (0.5 - center->lat / 180.0) * M_PI; /* needed rot. (in rad) */ + double cos_rot = cos(rot); /* cosine of rotation by latitude */ + double sin_rot = sin(rot); /* sine of rotation by latitude */ + double x_rot, z_rot; /* normalized coordinates of point on rotated spiral */ + double center_lon = center->lon; /* second rotation in degree */ + /* add safety margin to bounding circle because of spherical approximation */ + radius *= PGL_SPHEROID_A / PGL_RADIUS; + /* if whole earth is covered, use initialized value, otherwise calculate + share of covered area (multiplied by 2) */ + if (radius < PGL_MAXDIST) double_share = 1.0 - cos(radius / PGL_RADIUS); + /* divide double_share by sample count for later calculations */ + double_share_div_samples = double_share / samples; + /* generate sample points */ + for (i=0; i PGL_CLUSTER_MAXPOINTS) { + ereport(ERROR, ( + errcode(ERRCODE_DATA_EXCEPTION), + errmsg( + "too many sample points for Monte Carlo method (maximum %i)", + PGL_CLUSTER_MAXPOINTS + ) + )); + } + if (!(cluster->bounding.radius > 0)) return 0; + for (i=0; ; i++) { + if (i == cluster->nentries) return 0; + if (cluster->entries[i].entrytype == PGL_ENTRY_POLYGON) break; + } + points = palloc(samples * sizeof(pgl_point)); + area = pgl_sample_points( + &cluster->bounding.center, + cluster->bounding.radius, + samples, + points + ); + for (i=0; i PGL_CLUSTER_MAXPOINTS) { + ereport(ERROR, ( + errcode(ERRCODE_DATA_EXCEPTION), + errmsg( + "too many sample points for Monte Carlo method (maximum %i)", + PGL_CLUSTER_MAXPOINTS + ) + )); + } + /* calculate shortest distance from point to cluster */ + distance = pgl_point_cluster_distance(point, cluster); + /* if cluster consists of a single point or has no bounding circle with + positive radius, simply return distance */ + if ( + (cluster->nentries==1 && cluster->entries[0].entrytype==PGL_ENTRY_POINT) || + !(cluster->bounding.radius > 0) + ) return distance; + /* if cluster consists of two points which are twice as far apart, return + distance between point and cluster multiplied by square root of two */ + if ( + cluster->nentries == 2 && + cluster->entries[0].entrytype == PGL_ENTRY_POINT && + cluster->entries[1].entrytype == PGL_ENTRY_POINT && + pgl_distance( + PGL_ENTRY_POINTS(cluster, 0)[0].lat, + PGL_ENTRY_POINTS(cluster, 0)[0].lon, + PGL_ENTRY_POINTS(cluster, 1)[0].lat, + PGL_ENTRY_POINTS(cluster, 1)[0].lon + ) >= 2.0 * distance + ) { + return distance * M_SQRT2; + } + /* otherwise create sample points for Monte Carlo method and determine area + covered by sample points */ + points = palloc(samples * sizeof(pgl_point)); + area = pgl_sample_points( + &cluster->bounding.center, + cluster->bounding.radius + distance, /* pad bounding circle by distance */ + samples, + points + ); + /* perform Monte Carlo method */ + if (distance > 0) { + /* point is outside cluster */ + for (i=0; i distance) return result; + /* otherwise return distance between point and cluster */ + else return distance; +} + + +/*-------------------------------------------------* + * geographic index based on space-filling curve * + *-------------------------------------------------*/ + +/* number of bytes used for geographic (center) position in keys */ +#define PGL_KEY_LATLON_BYTELEN 7 + +/* maximum reference value for logarithmic size of geographic objects */ +#define PGL_AREAKEY_REFOBJSIZE (PGL_DIAMETER/3.0) /* can be tweaked */ + +/* pointer to index key (either pgl_pointkey or pgl_areakey) */ +typedef unsigned char *pgl_keyptr; + +/* index key for points (objects with zero area) on the spheroid */ +/* bit 0..55: interspersed bits of latitude and longitude, + bit 56..57: always zero, + bit 58..63: node depth in hypothetic (full) tree from 0 to 56 (incl.) */ +typedef unsigned char pgl_pointkey[PGL_KEY_LATLON_BYTELEN+1]; + +/* index key for geographic objects on spheroid with area greater than zero */ +/* bit 0..55: interspersed bits of latitude and longitude of center point, + bit 56: always set to 1, + bit 57..63: node depth in hypothetic (full) tree from 0 to (2*56)+1 (incl.), + bit 64..71: logarithmic object size from 0 to 56+1 = 57 (incl.), but set to + PGL_KEY_OBJSIZE_EMPTY (with interspersed bits = 0 and node depth + = 113) for empty objects, and set to PGL_KEY_OBJSIZE_UNIVERSAL + (with interspersed bits = 0 and node depth = 0) for keys which + cover both empty and non-empty objects */ + +typedef unsigned char pgl_areakey[PGL_KEY_LATLON_BYTELEN+2]; + +/* helper macros for reading/writing index keys */ +#define PGL_KEY_NODEDEPTH_OFFSET PGL_KEY_LATLON_BYTELEN +#define PGL_KEY_OBJSIZE_OFFSET (PGL_KEY_NODEDEPTH_OFFSET+1) +#define PGL_POINTKEY_MAXDEPTH (PGL_KEY_LATLON_BYTELEN*8) +#define PGL_AREAKEY_MAXDEPTH (2*PGL_POINTKEY_MAXDEPTH+1) +#define PGL_AREAKEY_MAXOBJSIZE (PGL_POINTKEY_MAXDEPTH+1) +#define PGL_AREAKEY_TYPEMASK 0x80 +#define PGL_KEY_LATLONBIT(key, n) ((key)[(n)/8] & (0x80 >> ((n)%8))) +#define PGL_KEY_LATLONBIT_DIFF(key1, key2, n) \ + ( PGL_KEY_LATLONBIT(key1, n) ^ \ + PGL_KEY_LATLONBIT(key2, n) ) +#define PGL_KEY_IS_AREAKEY(key) ((key)[PGL_KEY_NODEDEPTH_OFFSET] & \ + PGL_AREAKEY_TYPEMASK) +#define PGL_KEY_NODEDEPTH(key) ((key)[PGL_KEY_NODEDEPTH_OFFSET] & \ + (PGL_AREAKEY_TYPEMASK-1)) +#define PGL_KEY_OBJSIZE(key) ((key)[PGL_KEY_OBJSIZE_OFFSET]) +#define PGL_KEY_OBJSIZE_EMPTY 126 +#define PGL_KEY_OBJSIZE_UNIVERSAL 127 +#define PGL_KEY_IS_EMPTY(key) ( PGL_KEY_IS_AREAKEY(key) && \ + (key)[PGL_KEY_OBJSIZE_OFFSET] == \ + PGL_KEY_OBJSIZE_EMPTY ) +#define PGL_KEY_IS_UNIVERSAL(key) ( PGL_KEY_IS_AREAKEY(key) && \ + (key)[PGL_KEY_OBJSIZE_OFFSET] == \ + PGL_KEY_OBJSIZE_UNIVERSAL ) + +/* set area key to match empty objects only */ +static void pgl_key_set_empty(pgl_keyptr key) { + memset(key, 0, sizeof(pgl_areakey)); + /* Note: setting node depth to maximum is required for picksplit function */ + key[PGL_KEY_NODEDEPTH_OFFSET] = PGL_AREAKEY_TYPEMASK | PGL_AREAKEY_MAXDEPTH; + key[PGL_KEY_OBJSIZE_OFFSET] = PGL_KEY_OBJSIZE_EMPTY; +} + +/* set area key to match any object (including empty objects) */ +static void pgl_key_set_universal(pgl_keyptr key) { + memset(key, 0, sizeof(pgl_areakey)); + key[PGL_KEY_NODEDEPTH_OFFSET] = PGL_AREAKEY_TYPEMASK; + key[PGL_KEY_OBJSIZE_OFFSET] = PGL_KEY_OBJSIZE_UNIVERSAL; +} + +/* convert a point on earth into a max-depth key to be used in index */ +static void pgl_point_to_key(pgl_point *point, pgl_keyptr key) { + double lat = point->lat; + double lon = point->lon; + int i; + /* clear latitude and longitude bits */ + memset(key, 0, PGL_KEY_LATLON_BYTELEN); + /* set node depth to maximum and type bit to zero */ + key[PGL_KEY_NODEDEPTH_OFFSET] = PGL_POINTKEY_MAXDEPTH; + /* iterate over all latitude/longitude bit pairs */ + for (i=0; i= 0) { + key[i/4] |= 0x80 >> (2*(i%4)); + lat *= 2; lat -= 90; + } else { + lat *= 2; lat += 90; + } + /* determine longitude bit */ + if (lon >= 0) { + key[i/4] |= 0x80 >> (2*(i%4)+1); + lon *= 2; lon -= 180; + } else { + lon *= 2; lon += 180; + } + } +} + +/* convert a circle on earth into a max-depth key to be used in an index */ +static void pgl_circle_to_key(pgl_circle *circle, pgl_keyptr key) { + /* handle special case of empty circle */ + if (circle->radius < 0) { + pgl_key_set_empty(key); + return; + } + /* perform same action as for point keys */ + pgl_point_to_key(&(circle->center), key); + /* but overwrite type and node depth to fit area index key */ + key[PGL_KEY_NODEDEPTH_OFFSET] = PGL_AREAKEY_TYPEMASK | PGL_AREAKEY_MAXDEPTH; + /* check if radius is greater than (or equal to) reference size */ + /* (treat equal values as greater values for numerical safety) */ + if (circle->radius >= PGL_AREAKEY_REFOBJSIZE) { + /* if yes, set logarithmic size to zero */ + key[PGL_KEY_OBJSIZE_OFFSET] = 0; + } else { + /* otherwise, determine logarithmic size iteratively */ + /* (one step is equivalent to a factor of sqrt(2)) */ + double reference = PGL_AREAKEY_REFOBJSIZE / M_SQRT2; + int objsize = 1; + while (objsize < PGL_AREAKEY_MAXOBJSIZE) { + /* stop when radius is greater than (or equal to) adjusted reference */ + /* (treat equal values as greater values for numerical safety) */ + if (circle->radius >= reference) break; + reference /= M_SQRT2; + objsize++; + } + /* set logarithmic size to determined value */ + key[PGL_KEY_OBJSIZE_OFFSET] = objsize; + } +} + +/* check if one key is subkey of another key or vice versa */ +static bool pgl_keys_overlap(pgl_keyptr key1, pgl_keyptr key2) { + int i; /* key bit offset (includes both lat/lon and log. obj. size bits) */ + /* determine smallest depth */ + int depth1 = PGL_KEY_NODEDEPTH(key1); + int depth2 = PGL_KEY_NODEDEPTH(key2); + int depth = (depth1 < depth2) ? depth1 : depth2; + /* check if keys are area keys (assuming that both keys have same type) */ + if (PGL_KEY_IS_AREAKEY(key1)) { + int j = 0; /* bit offset for logarithmic object size bits */ + int k = 0; /* bit offset for latitude and longitude */ + /* fetch logarithmic object size information */ + int objsize1 = PGL_KEY_OBJSIZE(key1); + int objsize2 = PGL_KEY_OBJSIZE(key2); + /* handle special cases for empty objects (universal and empty keys) */ + if ( + objsize1 == PGL_KEY_OBJSIZE_UNIVERSAL || + objsize2 == PGL_KEY_OBJSIZE_UNIVERSAL + ) return true; + if ( + objsize1 == PGL_KEY_OBJSIZE_EMPTY || + objsize2 == PGL_KEY_OBJSIZE_EMPTY + ) return objsize1 == objsize2; + /* iterate through key bits */ + for (i=0; i j) || + (objsize2 <= j && objsize1 > j) + ) { + /* bit differs, therefore keys are in separate branches */ + return false; + } + /* increase bit counter for object size bits */ + j++; + } + /* all other bits describe latitude and longitude */ + else { + /* check if bit differs in both keys */ + if (PGL_KEY_LATLONBIT_DIFF(key1, key2, k)) { + /* bit differs, therefore keys are in separate branches */ + return false; + } + /* increase bit counter for latitude/longitude bits */ + k++; + } + } + } + /* if not, keys are point keys */ + else { + /* iterate through key bits */ + for (i=0; i PGL_AREAKEY_MAXOBJSIZE || + objsize2 > PGL_AREAKEY_MAXOBJSIZE + ) { + if ( + objsize1 == PGL_KEY_OBJSIZE_EMPTY && + objsize2 == PGL_KEY_OBJSIZE_EMPTY + ) pgl_key_set_empty(dst); + else pgl_key_set_universal(dst); + return; + } + /* iterate through key bits */ + for (i=0; i= j && objsize2 >= j) { + /* set objsize in destination buffer to indicate that size bit is + unset in destination buffer at the current bit position */ + dstbuf[PGL_KEY_OBJSIZE_OFFSET] = j; + } + /* break if object size bit is set in one key only */ + else if (objsize1 >= j || objsize2 >= j) break; + } + /* all other bits describe latitude and longitude */ + else { + /* break if bit differs in both keys */ + if (PGL_KEY_LATLONBIT(dst, k)) { + if (!PGL_KEY_LATLONBIT(src, k)) break; + /* but set bit in destination buffer if bit is set in both keys */ + dstbuf[k/8] |= 0x80 >> (k%8); + } else if (PGL_KEY_LATLONBIT(src, k)) break; + /* increase bit counter for latitude/longitude bits */ + k++; + } + } + /* set common node depth and type bit (type bit = 1) */ + dstbuf[PGL_KEY_NODEDEPTH_OFFSET] = PGL_AREAKEY_TYPEMASK | i; + /* copy contents of destination buffer to first key */ + memcpy(dst, dstbuf, sizeof(pgl_areakey)); + } + /* if not, keys are point keys */ + else { + pgl_pointkey dstbuf = { 0, }; /* destination buffer (cleared) */ + /* iterate through key bits */ + for (i=0; i> (i%8); + } else if (PGL_KEY_LATLONBIT(src, i)) break; + } + /* set common node depth (type bit = 0) */ + dstbuf[PGL_KEY_NODEDEPTH_OFFSET] = i; + /* copy contents of destination buffer to first key */ + memcpy(dst, dstbuf, sizeof(pgl_pointkey)); + } +} + +/* determine center(!) boundaries and radius estimation of index key */ +static double pgl_key_to_box(pgl_keyptr key, pgl_box *box) { + int i; + /* determine node depth */ + int depth = PGL_KEY_NODEDEPTH(key); + /* center point of possible result */ + double lat = 0; + double lon = 0; + /* maximum distance of real center point from key center */ + double dlat = 90; + double dlon = 180; + /* maximum radius of contained objects */ + double radius = 0; /* always return zero for point index keys */ + /* check if key is area key */ + if (PGL_KEY_IS_AREAKEY(key)) { + /* get logarithmic object size */ + int objsize = PGL_KEY_OBJSIZE(key); + /* handle special cases for empty objects (universal and empty keys) */ + if (objsize == PGL_KEY_OBJSIZE_EMPTY) { + pgl_box_set_empty(box); + return 0; + } else if (objsize == PGL_KEY_OBJSIZE_UNIVERSAL) { + box->lat_min = -90; + box->lat_max = 90; + box->lon_min = -180; + box->lon_max = 180; + return 0; /* any value >= 0 would do */ + } + /* calculate maximum possible radius of objects covered by the given key */ + if (objsize == 0) radius = INFINITY; + else { + radius = PGL_AREAKEY_REFOBJSIZE; + while (--objsize) radius /= M_SQRT2; + } + /* iterate over latitude and longitude bits in key */ + /* (every second bit is a latitude or longitude bit) */ + for (i=0; ilat_min = lat - dlat; + box->lat_max = lat + dlat; + box->lon_min = lon - dlon; + box->lon_max = lon + dlon; + /* return radius (as a function return value) */ + return radius; +} + +/* estimator function for distance between point and index key */ +/* always returns a smaller value than actually correct or zero */ +static double pgl_estimate_key_distance(pgl_keyptr key, pgl_point *point) { + pgl_box box; /* center(!) bounding box of area index key */ + /* calculate center(!) bounding box and maximum radius of objects covered + by area index key (radius is zero for point index keys) */ + double distance = pgl_key_to_box(key, &box); + /* calculate estimated distance between bounding box of center point of + indexed object and point passed as second argument, then substract maximum + radius of objects covered by index key */ + distance = pgl_estimate_point_box_distance(point, &box) - distance; + /* truncate negative results to zero */ + if (distance <= 0) distance = 0; + /* return result */ + return distance; +} + + +/*---------------------------------* + * helper functions for text I/O * + *---------------------------------*/ + +#define PGL_NUMBUFLEN 64 /* buffer size for number to string conversion */ + +/* convert floating point number to string (round-trip safe) */ +static void pgl_print_float(char *buf, double flt) { + /* check if number is integral */ + if (trunc(flt) == flt) { + /* for integral floats use maximum precision */ + snprintf(buf, PGL_NUMBUFLEN, "%.17g", flt); + } else { + /* otherwise check if 15, 16, or 17 digits needed (round-trip safety) */ + snprintf(buf, PGL_NUMBUFLEN, "%.15g", flt); + if (strtod(buf, NULL) != flt) snprintf(buf, PGL_NUMBUFLEN, "%.16g", flt); + if (strtod(buf, NULL) != flt) snprintf(buf, PGL_NUMBUFLEN, "%.17g", flt); + } +} + +/* convert latitude floating point number (in degrees) to string */ +static void pgl_print_lat(char *buf, double lat) { + if (signbit(lat)) { + /* treat negative latitudes (including -0) as south */ + snprintf(buf, PGL_NUMBUFLEN, "S%015.12f", -lat); + } else { + /* treat positive latitudes (including +0) as north */ + snprintf(buf, PGL_NUMBUFLEN, "N%015.12f", lat); + } +} + +/* convert longitude floating point number (in degrees) to string */ +static void pgl_print_lon(char *buf, double lon) { + if (signbit(lon)) { + /* treat negative longitudes (including -0) as west */ + snprintf(buf, PGL_NUMBUFLEN, "W%016.12f", -lon); + } else { + /* treat positive longitudes (including +0) as east */ + snprintf(buf, PGL_NUMBUFLEN, "E%016.12f", lon); + } +} + +/* bit masks used as return value of pgl_scan() function */ +#define PGL_SCAN_NONE 0 /* no value has been parsed */ +#define PGL_SCAN_LAT (1<<0) /* latitude has been parsed */ +#define PGL_SCAN_LON (1<<1) /* longitude has been parsed */ +#define PGL_SCAN_LATLON (PGL_SCAN_LAT | PGL_SCAN_LON) /* bitwise OR of both */ + +/* parse a coordinate (can be latitude or longitude) */ +static int pgl_scan(char **str, double *lat, double *lon) { + double val; + int len; + if ( + sscanf(*str, " N %lf %n", &val, &len) || + sscanf(*str, " n %lf %n", &val, &len) + ) { + *str += len; *lat = val; return PGL_SCAN_LAT; + } + if ( + sscanf(*str, " S %lf %n", &val, &len) || + sscanf(*str, " s %lf %n", &val, &len) + ) { + *str += len; *lat = -val; return PGL_SCAN_LAT; + } + if ( + sscanf(*str, " E %lf %n", &val, &len) || + sscanf(*str, " e %lf %n", &val, &len) + ) { + *str += len; *lon = val; return PGL_SCAN_LON; + } + if ( + sscanf(*str, " W %lf %n", &val, &len) || + sscanf(*str, " w %lf %n", &val, &len) + ) { + *str += len; *lon = -val; return PGL_SCAN_LON; + } + return PGL_SCAN_NONE; +} + + +/*-----------------* + * SQL functions * + *-----------------*/ + +/* Note: These function names use "epoint", "ebox", etc. notation here instead + of "point", "box", etc. in order to distinguish them from any previously + defined functions. */ + +/* function needed for dummy types and/or not implemented features */ +PG_FUNCTION_INFO_V1(pgl_notimpl); +Datum pgl_notimpl(PG_FUNCTION_ARGS) { + ereport(ERROR, (errmsg("not implemented by pgLatLon"))); +} + +/* set point to latitude and longitude (including checks) */ +static void pgl_epoint_set_latlon(pgl_point *point, double lat, double lon) { + /* reject infinite or NaN values */ + if (!isfinite(lat) || !isfinite(lon)) { + ereport(ERROR, ( + errcode(ERRCODE_DATA_EXCEPTION), + errmsg("epoint requires finite coordinates") + )); + } + /* check latitude bounds */ + if (lat < -90) { + ereport(WARNING, (errmsg("latitude exceeds south pole"))); + lat = -90; + } else if (lat > 90) { + ereport(WARNING, (errmsg("latitude exceeds north pole"))); + lat = 90; + } + /* check longitude bounds */ + if (lon < -180) { + ereport(NOTICE, (errmsg("longitude west of 180th meridian normalized"))); + lon += 360 - trunc(lon / 360) * 360; + } else if (lon > 180) { + ereport(NOTICE, (errmsg("longitude east of 180th meridian normalized"))); + lon -= 360 + trunc(lon / 360) * 360; + } + /* store rounded latitude/longitude values for round-trip safety */ + point->lat = pgl_round(lat); + point->lon = pgl_round(lon); +} + +/* create point ("epoint" in SQL) from latitude and longitude */ +PG_FUNCTION_INFO_V1(pgl_create_epoint); +Datum pgl_create_epoint(PG_FUNCTION_ARGS) { + pgl_point *point = (pgl_point *)palloc(sizeof(pgl_point)); + pgl_epoint_set_latlon(point, PG_GETARG_FLOAT8(0), PG_GETARG_FLOAT8(1)); + PG_RETURN_POINTER(point); +} + +/* parse point ("epoint" in SQL) */ +/* format: '[NS] [EW]' */ +PG_FUNCTION_INFO_V1(pgl_epoint_in); +Datum pgl_epoint_in(PG_FUNCTION_ARGS) { + char *str = PG_GETARG_CSTRING(0); /* input string */ + char *strptr = str; /* current position within string */ + int done = 0; /* bit mask storing if latitude or longitude was read */ + double lat, lon; /* parsed values as double precision floats */ + pgl_point *point; /* return value (to be palloc'ed) */ + /* parse two floats (each latitude or longitude) separated by white-space */ + done |= pgl_scan(&strptr, &lat, &lon); + if (strptr != str && isspace(strptr[-1])) { + done |= pgl_scan(&strptr, &lat, &lon); + } + /* require end of string, and latitude and longitude parsed successfully */ + if (strptr[0] || done != PGL_SCAN_LATLON) { + ereport(ERROR, ( + errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type epoint: \"%s\"", str) + )); + } + /* allocate memory for result */ + point = (pgl_point *)palloc(sizeof(pgl_point)); + /* set latitude and longitude (and perform checks) */ + pgl_epoint_set_latlon(point, lat, lon); + /* return result */ + PG_RETURN_POINTER(point); +} + +/* create box ("ebox" in SQL) that is empty */ +PG_FUNCTION_INFO_V1(pgl_create_empty_ebox); +Datum pgl_create_empty_ebox(PG_FUNCTION_ARGS) { + pgl_box *box = (pgl_box *)palloc(sizeof(pgl_box)); + pgl_box_set_empty(box); + PG_RETURN_POINTER(box); +} + +/* set box to given boundaries (including checks) */ +static void pgl_ebox_set_boundaries( + pgl_box *box, + double lat_min, double lat_max, double lon_min, double lon_max +) { + /* if minimum latitude is greater than maximum latitude, return empty box */ + if (lat_min > lat_max) { + pgl_box_set_empty(box); + return; + } + /* otherwise reject infinite or NaN values */ + if ( + !isfinite(lat_min) || !isfinite(lat_max) || + !isfinite(lon_min) || !isfinite(lon_max) + ) { + ereport(ERROR, ( + errcode(ERRCODE_DATA_EXCEPTION), + errmsg("ebox requires finite coordinates") + )); + } + /* check latitude bounds */ + if (lat_max < -90) { + ereport(WARNING, (errmsg("northern latitude exceeds south pole"))); + lat_max = -90; + } else if (lat_max > 90) { + ereport(WARNING, (errmsg("northern latitude exceeds north pole"))); + lat_max = 90; + } + if (lat_min < -90) { + ereport(WARNING, (errmsg("southern latitude exceeds south pole"))); + lat_min = -90; + } else if (lat_min > 90) { + ereport(WARNING, (errmsg("southern latitude exceeds north pole"))); + lat_min = 90; + } + /* check if all longitudes are included */ + if (lon_max - lon_min >= 360) { + if (lon_max - lon_min > 360) ereport(WARNING, ( + errmsg("longitude coverage greater than 360 degrees") + )); + lon_min = -180; + lon_max = 180; + } else { + /* normalize longitude bounds */ + if (lon_min < -180) lon_min += 360 - trunc(lon_min / 360) * 360; + else if (lon_min > 180) lon_min -= 360 + trunc(lon_min / 360) * 360; + if (lon_max < -180) lon_max += 360 - trunc(lon_max / 360) * 360; + else if (lon_max > 180) lon_max -= 360 + trunc(lon_max / 360) * 360; + } + /* store rounded latitude/longitude values for round-trip safety */ + box->lat_min = pgl_round(lat_min); + box->lat_max = pgl_round(lat_max); + box->lon_min = pgl_round(lon_min); + box->lon_max = pgl_round(lon_max); + /* ensure that rounding does not change orientation */ + if (lon_min > lon_max && box->lon_min == box->lon_max) { + box->lon_min = -180; + box->lon_max = 180; + } +} + +/* create box ("ebox" in SQL) from min/max latitude and min/max longitude */ +PG_FUNCTION_INFO_V1(pgl_create_ebox); +Datum pgl_create_ebox(PG_FUNCTION_ARGS) { + pgl_box *box = (pgl_box *)palloc(sizeof(pgl_box)); + pgl_ebox_set_boundaries( + box, + PG_GETARG_FLOAT8(0), PG_GETARG_FLOAT8(1), + PG_GETARG_FLOAT8(2), PG_GETARG_FLOAT8(3) + ); + PG_RETURN_POINTER(box); +} + +/* create box ("ebox" in SQL) from two points ("epoint"s) */ +/* (can not be used to cover a longitude range of more than 120 degrees) */ +PG_FUNCTION_INFO_V1(pgl_create_ebox_from_epoints); +Datum pgl_create_ebox_from_epoints(PG_FUNCTION_ARGS) { + pgl_point *point1 = (pgl_point *)PG_GETARG_POINTER(0); + pgl_point *point2 = (pgl_point *)PG_GETARG_POINTER(1); + pgl_box *box = (pgl_box *)palloc(sizeof(pgl_box)); + double lat_min, lat_max, lon_min, lon_max; + double dlon; /* longitude range (delta longitude) */ + /* order latitude and longitude boundaries */ + if (point2->lat < point1->lat) { + lat_min = point2->lat; + lat_max = point1->lat; + } else { + lat_min = point1->lat; + lat_max = point2->lat; + } + if (point2->lon < point1->lon) { + lon_min = point2->lon; + lon_max = point1->lon; + } else { + lon_min = point1->lon; + lon_max = point2->lon; + } + /* calculate longitude range (round to avoid floating point errors) */ + dlon = pgl_round(lon_max - lon_min); + /* determine east-west direction */ + if (dlon >= 240) { + /* assume that 180th meridian is crossed and swap min/max longitude */ + double swap = lon_min; lon_min = lon_max; lon_max = swap; + } else if (dlon > 120) { + /* unclear orientation since delta longitude > 120 */ + ereport(ERROR, ( + errcode(ERRCODE_DATA_EXCEPTION), + errmsg("can not determine east/west orientation for ebox") + )); + } + /* use boundaries to setup box (and perform checks) */ + pgl_ebox_set_boundaries(box, lat_min, lat_max, lon_min, lon_max); + /* return result */ + PG_RETURN_POINTER(box); +} + +/* parse box ("ebox" in SQL) */ +/* format: '[NS] [EW] [NS] [EW]' + or: '[NS] [NS] [EW] [EW]' */ +PG_FUNCTION_INFO_V1(pgl_ebox_in); +Datum pgl_ebox_in(PG_FUNCTION_ARGS) { + char *str = PG_GETARG_CSTRING(0); /* input string */ + char *str_lower; /* lower case version of input string */ + char *strptr; /* current position within string */ + int valid; /* number of valid chars */ + int done; /* specifies if latitude or longitude was read */ + double val; /* temporary variable */ + int lat_count = 0; /* count of latitude values parsed */ + int lon_count = 0; /* count of longitufde values parsed */ + double lat_min, lat_max, lon_min, lon_max; /* see pgl_box struct */ + pgl_box *box; /* return value (to be palloc'ed) */ + /* lowercase input */ + str_lower = psprintf("%s", str); + for (strptr=str_lower; *strptr; strptr++) { + if (*strptr >= 'A' && *strptr <= 'Z') *strptr += 'a' - 'A'; + } + /* reset reading position to start of (lowercase) string */ + strptr = str_lower; + /* check if empty box */ + valid = 0; + sscanf(strptr, " empty %n", &valid); + if (valid && strptr[valid] == 0) { + /* allocate and return empty box */ + box = (pgl_box *)palloc(sizeof(pgl_box)); + pgl_box_set_empty(box); + PG_RETURN_POINTER(box); + } + /* demand four blocks separated by whitespace */ + valid = 0; + sscanf(strptr, " %*s %*s %*s %*s %n", &valid); + /* if four blocks separated by whitespace exist, parse those blocks */ + if (strptr[valid] == 0) while (strptr[0]) { + /* parse either latitude or longitude (whichever found in input string) */ + done = pgl_scan(&strptr, &val, &val); + /* store latitude or longitude in lat_min, lat_max, lon_min, or lon_max */ + if (done == PGL_SCAN_LAT) { + if (!lat_count) lat_min = val; else lat_max = val; + lat_count++; + } else if (done == PGL_SCAN_LON) { + if (!lon_count) lon_min = val; else lon_max = val; + lon_count++; + } else { + break; + } + } + /* require end of string, and two latitude and two longitude values */ + if (strptr[0] || lat_count != 2 || lon_count != 2) { + ereport(ERROR, ( + errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type ebox: \"%s\"", str) + )); + } + /* free lower case string */ + pfree(str_lower); + /* order boundaries (maximum greater than minimum) */ + if (lat_min > lat_max) { val = lat_min; lat_min = lat_max; lat_max = val; } + if (lon_min > lon_max) { val = lon_min; lon_min = lon_max; lon_max = val; } + /* allocate memory for result */ + box = (pgl_box *)palloc(sizeof(pgl_box)); + /* set boundaries (and perform checks) */ + pgl_ebox_set_boundaries(box, lat_min, lat_max, lon_min, lon_max); + /* return result */ + PG_RETURN_POINTER(box); +} + +/* set circle to given latitude, longitude, and radius (including checks) */ +static void pgl_ecircle_set_latlon_radius( + pgl_circle *circle, double lat, double lon, double radius +) { + /* set center point (including checks) */ + pgl_epoint_set_latlon(&(circle->center), lat, lon); + /* handle non-positive radius */ + if (isnan(radius)) { + ereport(ERROR, ( + errcode(ERRCODE_DATA_EXCEPTION), + errmsg("invalid radius for ecircle") + )); + } + if (radius == 0) radius = 0; /* avoids -0 */ + else if (radius < 0) { + if (isfinite(radius)) { + ereport(NOTICE, (errmsg("negative radius converted to minus infinity"))); + } + radius = -INFINITY; + } + /* store radius (round-trip safety is ensured by pgl_print_float) */ + circle->radius = radius; +} + +/* create circle ("ecircle" in SQL) from latitude, longitude, and radius */ +PG_FUNCTION_INFO_V1(pgl_create_ecircle); +Datum pgl_create_ecircle(PG_FUNCTION_ARGS) { + pgl_circle *circle = (pgl_circle *)palloc(sizeof(pgl_circle)); + pgl_ecircle_set_latlon_radius( + circle, PG_GETARG_FLOAT8(0), PG_GETARG_FLOAT8(1), PG_GETARG_FLOAT8(2) + ); + PG_RETURN_POINTER(circle); +} + +/* create circle ("ecircle" in SQL) from point ("epoint"), and radius */ +PG_FUNCTION_INFO_V1(pgl_create_ecircle_from_epoint); +Datum pgl_create_ecircle_from_epoint(PG_FUNCTION_ARGS) { + pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); + double radius = PG_GETARG_FLOAT8(1); + pgl_circle *circle = (pgl_circle *)palloc(sizeof(pgl_circle)); + /* set latitude, longitude, radius (and perform checks) */ + pgl_ecircle_set_latlon_radius(circle, point->lat, point->lon, radius); + /* return result */ + PG_RETURN_POINTER(circle); +} + +/* parse circle ("ecircle" in SQL) */ +/* format: '[NS] [EW] ' */ +PG_FUNCTION_INFO_V1(pgl_ecircle_in); +Datum pgl_ecircle_in(PG_FUNCTION_ARGS) { + char *str = PG_GETARG_CSTRING(0); /* input string */ + char *strptr = str; /* current position within string */ + double lat, lon, radius; /* parsed values as double precision flaots */ + int valid = 0; /* number of valid chars */ + int done = 0; /* stores if latitude and/or longitude was read */ + pgl_circle *circle; /* return value (to be palloc'ed) */ + /* demand three blocks separated by whitespace */ + sscanf(strptr, " %*s %*s %*s %n", &valid); + /* if three blocks separated by whitespace exist, parse those blocks */ + if (strptr[valid] == 0) { + /* parse latitude and longitude */ + done |= pgl_scan(&strptr, &lat, &lon); + done |= pgl_scan(&strptr, &lat, &lon); + /* parse radius (while incrementing strptr by number of bytes parsed) */ + valid = 0; + if (sscanf(strptr, " %lf %n", &radius, &valid) == 1) strptr += valid; + } + /* require end of string and both latitude and longitude being parsed */ + if (strptr[0] || done != PGL_SCAN_LATLON) { + ereport(ERROR, ( + errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type ecircle: \"%s\"", str) + )); + } + /* allocate memory for result */ + circle = (pgl_circle *)palloc(sizeof(pgl_circle)); + /* set latitude, longitude, radius (and perform checks) */ + pgl_ecircle_set_latlon_radius(circle, lat, lon, radius); + /* return result */ + PG_RETURN_POINTER(circle); +} + +/* parse cluster ("ecluster" in SQL) */ +PG_FUNCTION_INFO_V1(pgl_ecluster_in); +Datum pgl_ecluster_in(PG_FUNCTION_ARGS) { + int i; + char *str = PG_GETARG_CSTRING(0); /* input string */ + char *str_lower; /* lower case version of input string */ + char *strptr; /* pointer to current reading position of input */ + int npoints_total = 0; /* total number of points in cluster */ + int nentries = 0; /* total number of entries */ + pgl_newentry *entries; /* array of pgl_newentry to create pgl_cluster */ + int entries_buflen = 4; /* maximum number of elements in entries array */ + int valid; /* number of valid chars processed */ + double lat, lon; /* latitude and longitude of parsed point */ + int entrytype; /* current entry type */ + int npoints; /* number of points in current entry */ + pgl_point *points; /* array of pgl_point for pgl_newentry */ + int points_buflen; /* maximum number of elements in points array */ + int done; /* return value of pgl_scan function */ + pgl_cluster *cluster; /* created cluster */ + /* lowercase input */ + str_lower = psprintf("%s", str); + for (strptr=str_lower; *strptr; strptr++) { + if (*strptr >= 'A' && *strptr <= 'Z') *strptr += 'a' - 'A'; + } + /* reset reading position to start of (lowercase) string */ + strptr = str_lower; + /* allocate initial buffer for entries */ + entries = palloc(entries_buflen * sizeof(pgl_newentry)); + /* parse until end of string */ + while (strptr[0]) { + /* require previous white-space or closing parenthesis before next token */ + if (strptr != str_lower && !isspace(strptr[-1]) && strptr[-1] != ')') { + goto pgl_ecluster_in_error; + } + /* ignore token "empty" */ + valid = 0; sscanf(strptr, " empty %n", &valid); + if (valid) { strptr += valid; continue; } + /* test for "point" token */ + valid = 0; sscanf(strptr, " point ( %n", &valid); + if (valid) { + strptr += valid; + entrytype = PGL_ENTRY_POINT; + goto pgl_ecluster_in_type_ok; + } + /* test for "path" token */ + valid = 0; sscanf(strptr, " path ( %n", &valid); + if (valid) { + strptr += valid; + entrytype = PGL_ENTRY_PATH; + goto pgl_ecluster_in_type_ok; + } + /* test for "outline" token */ + valid = 0; sscanf(strptr, " outline ( %n", &valid); + if (valid) { + strptr += valid; + entrytype = PGL_ENTRY_OUTLINE; + goto pgl_ecluster_in_type_ok; + } + /* test for "polygon" token */ + valid = 0; sscanf(strptr, " polygon ( %n", &valid); + if (valid) { + strptr += valid; + entrytype = PGL_ENTRY_POLYGON; + goto pgl_ecluster_in_type_ok; + } + /* error if no valid token found */ + goto pgl_ecluster_in_error; + pgl_ecluster_in_type_ok: + /* check if pgl_newentry array needs to grow */ + if (nentries == entries_buflen) { + pgl_newentry *newbuf; + entries_buflen *= 2; + newbuf = palloc(entries_buflen * sizeof(pgl_newentry)); + memcpy(newbuf, entries, nentries * sizeof(pgl_newentry)); + pfree(entries); + entries = newbuf; + } + /* reset number of points for current entry */ + npoints = 0; + /* allocate array for points */ + points_buflen = 4; + points = palloc(points_buflen * sizeof(pgl_point)); + /* parse until closing parenthesis */ + while (strptr[0] != ')') { + /* error on unexpected end of string */ + if (strptr[0] == 0) goto pgl_ecluster_in_error; + /* mark neither latitude nor longitude as read */ + done = PGL_SCAN_NONE; + /* require white-space before second, third, etc. point */ + if (npoints != 0 && !isspace(strptr[-1])) goto pgl_ecluster_in_error; + /* scan latitude (or longitude) */ + done |= pgl_scan(&strptr, &lat, &lon); + /* require white-space before second coordinate */ + if (strptr != str && !isspace(strptr[-1])) goto pgl_ecluster_in_error; + /* scan longitude (or latitude) */ + done |= pgl_scan(&strptr, &lat, &lon); + /* error unless both latitude and longitude were parsed */ + if (done != PGL_SCAN_LATLON) goto pgl_ecluster_in_error; + /* throw error if number of points is too high */ + if (npoints_total == PGL_CLUSTER_MAXPOINTS) { + ereport(ERROR, ( + errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg( + "too many points for ecluster entry (maximum %i)", + PGL_CLUSTER_MAXPOINTS + ) + )); + } + /* check if pgl_point array needs to grow */ + if (npoints == points_buflen) { + pgl_point *newbuf; + points_buflen *= 2; + newbuf = palloc(points_buflen * sizeof(pgl_point)); + memcpy(newbuf, points, npoints * sizeof(pgl_point)); + pfree(points); + points = newbuf; + } + /* append point to pgl_point array (includes checks) */ + pgl_epoint_set_latlon(&(points[npoints++]), lat, lon); + /* increase total number of points */ + npoints_total++; + } + /* error if entry has no points */ + if (!npoints) goto pgl_ecluster_in_error; + /* entries with one point are automatically of type "point" */ + if (npoints == 1) entrytype = PGL_ENTRY_POINT; + /* if entries have more than one point */ + else { + /* throw error if entry type is "point" */ + if (entrytype == PGL_ENTRY_POINT) { + ereport(ERROR, ( + errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type ecluster (point entry with more than one point)") + )); + } + /* coerce outlines and polygons with more than 2 points to be a path */ + if (npoints == 2) entrytype = PGL_ENTRY_PATH; + } + /* append entry to pgl_newentry array */ + entries[nentries].entrytype = entrytype; + entries[nentries].npoints = npoints; + entries[nentries].points = points; + nentries++; + /* consume closing parenthesis */ + strptr++; + /* consume white-space */ + while (isspace(strptr[0])) strptr++; + } + /* free lower case string */ + pfree(str_lower); + /* create cluster from pgl_newentry array */ + cluster = pgl_new_cluster(nentries, entries); + /* free pgl_newentry array */ + for (i=0; ilat); + pgl_print_lon(lonstr, point->lon); + PG_RETURN_CSTRING(psprintf("%s %s", latstr, lonstr)); +} + +/* convert box ("ebox") to string representation */ +PG_FUNCTION_INFO_V1(pgl_ebox_out); +Datum pgl_ebox_out(PG_FUNCTION_ARGS) { + pgl_box *box = (pgl_box *)PG_GETARG_POINTER(0); + double lon_min = box->lon_min; + double lon_max = box->lon_max; + char lat_min_str[PGL_NUMBUFLEN]; + char lat_max_str[PGL_NUMBUFLEN]; + char lon_min_str[PGL_NUMBUFLEN]; + char lon_max_str[PGL_NUMBUFLEN]; + /* return string "empty" if box is set to be empty */ + if (box->lat_min > box->lat_max) PG_RETURN_CSTRING("empty"); + /* use boundaries exceeding W180 or E180 if 180th meridian is enclosed */ + /* (required since pgl_box_in orders the longitude boundaries) */ + if (lon_min > lon_max) { + if (lon_min + lon_max >= 0) lon_min -= 360; + else lon_max += 360; + } + /* format and return result */ + pgl_print_lat(lat_min_str, box->lat_min); + pgl_print_lat(lat_max_str, box->lat_max); + pgl_print_lon(lon_min_str, lon_min); + pgl_print_lon(lon_max_str, lon_max); + PG_RETURN_CSTRING(psprintf( + "%s %s %s %s", + lat_min_str, lon_min_str, lat_max_str, lon_max_str + )); +} + +/* convert circle ("ecircle") to string representation */ +PG_FUNCTION_INFO_V1(pgl_ecircle_out); +Datum pgl_ecircle_out(PG_FUNCTION_ARGS) { + pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0); + char latstr[PGL_NUMBUFLEN]; + char lonstr[PGL_NUMBUFLEN]; + char radstr[PGL_NUMBUFLEN]; + pgl_print_lat(latstr, circle->center.lat); + pgl_print_lon(lonstr, circle->center.lon); + pgl_print_float(radstr, circle->radius); + PG_RETURN_CSTRING(psprintf("%s %s %s", latstr, lonstr, radstr)); +} + +/* convert cluster ("ecluster") to string representation */ +PG_FUNCTION_INFO_V1(pgl_ecluster_out); +Datum pgl_ecluster_out(PG_FUNCTION_ARGS) { + pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + char latstr[PGL_NUMBUFLEN]; /* string buffer for latitude */ + char lonstr[PGL_NUMBUFLEN]; /* string buffer for longitude */ + char ***strings; /* array of array of strings */ + char *string; /* string of current token */ + char *res, *resptr; /* result and pointer to current write position */ + size_t reslen = 1; /* length of result (init with 1 for terminator) */ + int npoints; /* number of points of current entry */ + int i, j; /* i: entry, j: point in entry */ + /* handle empty clusters */ + if (cluster->nentries == 0) { + /* free detoasted cluster (if copy) */ + PG_FREE_IF_COPY(cluster, 0); + /* return static result */ + PG_RETURN_CSTRING("empty"); + } + /* allocate array of array of strings */ + strings = palloc(cluster->nentries * sizeof(char **)); + /* iterate over all entries in cluster */ + for (i=0; inentries; i++) { + /* get number of points in entry */ + npoints = cluster->entries[i].npoints; + /* allocate array of strings (one string for each point plus two extra) */ + strings[i] = palloc((2 + npoints) * sizeof(char *)); + /* determine opening string */ + switch (cluster->entries[i].entrytype) { + case PGL_ENTRY_POINT: string = (i==0)?"point (" :" point ("; break; + case PGL_ENTRY_PATH: string = (i==0)?"path (" :" path ("; break; + case PGL_ENTRY_OUTLINE: string = (i==0)?"outline (":" outline ("; break; + case PGL_ENTRY_POLYGON: string = (i==0)?"polygon (":" polygon ("; break; + default: string = (i==0)?"unknown" :" unknown"; + } + /* use opening string as first string in array */ + strings[i][0] = string; + /* update result length (for allocating result string later) */ + reslen += strlen(string); + /* iterate over all points */ + for (j=0; jnentries; i++) { + npoints = cluster->entries[i].npoints; + for (j=0; jlat = pq_getmsgfloat8(buf); + point->lon = pq_getmsgfloat8(buf); + PG_RETURN_POINTER(point); +} + +/* binary input function for box ("ebox") */ +PG_FUNCTION_INFO_V1(pgl_ebox_recv); +Datum pgl_ebox_recv(PG_FUNCTION_ARGS) { + StringInfo buf = (StringInfo)PG_GETARG_POINTER(0); + pgl_box *box = (pgl_box *)palloc(sizeof(pgl_box)); + box->lat_min = pq_getmsgfloat8(buf); + box->lat_max = pq_getmsgfloat8(buf); + box->lon_min = pq_getmsgfloat8(buf); + box->lon_max = pq_getmsgfloat8(buf); + PG_RETURN_POINTER(box); +} + +/* binary input function for circle ("ecircle") */ +PG_FUNCTION_INFO_V1(pgl_ecircle_recv); +Datum pgl_ecircle_recv(PG_FUNCTION_ARGS) { + StringInfo buf = (StringInfo)PG_GETARG_POINTER(0); + pgl_circle *circle = (pgl_circle *)palloc(sizeof(pgl_circle)); + circle->center.lat = pq_getmsgfloat8(buf); + circle->center.lon = pq_getmsgfloat8(buf); + circle->radius = pq_getmsgfloat8(buf); + PG_RETURN_POINTER(circle); +} + +/* TODO: binary receive function for cluster */ + +/* binary output function for point ("epoint") */ +PG_FUNCTION_INFO_V1(pgl_epoint_send); +Datum pgl_epoint_send(PG_FUNCTION_ARGS) { + pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); + StringInfoData buf; + pq_begintypsend(&buf); + pq_sendfloat8(&buf, point->lat); + pq_sendfloat8(&buf, point->lon); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* binary output function for box ("ebox") */ +PG_FUNCTION_INFO_V1(pgl_ebox_send); +Datum pgl_ebox_send(PG_FUNCTION_ARGS) { + pgl_box *box = (pgl_box *)PG_GETARG_POINTER(0); + StringInfoData buf; + pq_begintypsend(&buf); + pq_sendfloat8(&buf, box->lat_min); + pq_sendfloat8(&buf, box->lat_max); + pq_sendfloat8(&buf, box->lon_min); + pq_sendfloat8(&buf, box->lon_max); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* binary output function for circle ("ecircle") */ +PG_FUNCTION_INFO_V1(pgl_ecircle_send); +Datum pgl_ecircle_send(PG_FUNCTION_ARGS) { + pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0); + StringInfoData buf; + pq_begintypsend(&buf); + pq_sendfloat8(&buf, circle->center.lat); + pq_sendfloat8(&buf, circle->center.lon); + pq_sendfloat8(&buf, circle->radius); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* TODO: binary send functions for cluster */ + +/* cast point ("epoint") to box ("ebox") */ +PG_FUNCTION_INFO_V1(pgl_epoint_to_ebox); +Datum pgl_epoint_to_ebox(PG_FUNCTION_ARGS) { + pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); + pgl_box *box = palloc(sizeof(pgl_box)); + box->lat_min = point->lat; + box->lat_max = point->lat; + box->lon_min = point->lon; + box->lon_max = point->lon; + PG_RETURN_POINTER(box); +} + +/* cast point ("epoint") to circle ("ecircle") */ +PG_FUNCTION_INFO_V1(pgl_epoint_to_ecircle); +Datum pgl_epoint_to_ecircle(PG_FUNCTION_ARGS) { + pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); + pgl_circle *circle = palloc(sizeof(pgl_box)); + circle->center = *point; + circle->radius = 0; + PG_RETURN_POINTER(circle); +} + +/* cast point ("epoint") to cluster ("ecluster") */ +PG_FUNCTION_INFO_V1(pgl_epoint_to_ecluster); +Datum pgl_epoint_to_ecluster(PG_FUNCTION_ARGS) { + pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); + pgl_newentry entry; + pgl_cluster *cluster; + entry.entrytype = PGL_ENTRY_POINT; + entry.npoints = 1; + entry.points = point; + cluster = pgl_new_cluster(1, &entry); + pgl_finalize_cluster(cluster); /* NOTE: should not fail */ + PG_RETURN_POINTER(cluster); +} + +/* cast box ("ebox") to cluster ("ecluster") */ +#define pgl_ebox_to_ecluster_macro(i, a, b) \ + entries[i].entrytype = PGL_ENTRY_POLYGON; \ + entries[i].npoints = 4; \ + entries[i].points = points[i]; \ + points[i][0].lat = box->lat_min; \ + points[i][0].lon = (a); \ + points[i][1].lat = box->lat_min; \ + points[i][1].lon = (b); \ + points[i][2].lat = box->lat_max; \ + points[i][2].lon = (b); \ + points[i][3].lat = box->lat_max; \ + points[i][3].lon = (a); +PG_FUNCTION_INFO_V1(pgl_ebox_to_ecluster); +Datum pgl_ebox_to_ecluster(PG_FUNCTION_ARGS) { + pgl_box *box = (pgl_box *)PG_GETARG_POINTER(0); + double lon, dlon; + int nentries; + pgl_newentry entries[3]; + pgl_point points[3][4]; + pgl_cluster *cluster; + if (box->lat_min > box->lat_max) { + nentries = 0; + } else if (box->lon_min > box->lon_max) { + if (box->lon_min < 0) { + lon = pgl_round((box->lon_min + 180) / 2.0); + nentries = 3; + pgl_ebox_to_ecluster_macro(0, box->lon_min, lon); + pgl_ebox_to_ecluster_macro(1, lon, 180); + pgl_ebox_to_ecluster_macro(2, -180, box->lon_max); + } else if (box->lon_max > 0) { + lon = pgl_round((box->lon_max - 180) / 2.0); + nentries = 3; + pgl_ebox_to_ecluster_macro(0, box->lon_min, 180); + pgl_ebox_to_ecluster_macro(1, -180, lon); + pgl_ebox_to_ecluster_macro(2, lon, box->lon_max); + } else { + nentries = 2; + pgl_ebox_to_ecluster_macro(0, box->lon_min, 180); + pgl_ebox_to_ecluster_macro(1, -180, box->lon_max); + } + } else { + dlon = pgl_round(box->lon_max - box->lon_min); + if (dlon < 180) { + nentries = 1; + pgl_ebox_to_ecluster_macro(0, box->lon_min, box->lon_max); + } else { + lon = pgl_round((box->lon_min + box->lon_max) / 2.0); + if ( + pgl_round(lon - box->lon_min) < 180 && + pgl_round(box->lon_max - lon) < 180 + ) { + nentries = 2; + pgl_ebox_to_ecluster_macro(0, box->lon_min, lon); + pgl_ebox_to_ecluster_macro(1, lon, box->lon_max); + } else { + nentries = 3; + pgl_ebox_to_ecluster_macro(0, box->lon_min, -60); + pgl_ebox_to_ecluster_macro(1, -60, 60); + pgl_ebox_to_ecluster_macro(2, 60, box->lon_max); + } + } + } + cluster = pgl_new_cluster(nentries, entries); + pgl_finalize_cluster(cluster); /* NOTE: should not fail */ + PG_RETURN_POINTER(cluster); +} + +/* extract latitude from point ("epoint") */ +PG_FUNCTION_INFO_V1(pgl_epoint_lat); +Datum pgl_epoint_lat(PG_FUNCTION_ARGS) { + PG_RETURN_FLOAT8(((pgl_point *)PG_GETARG_POINTER(0))->lat); +} + +/* extract longitude from point ("epoint") */ +PG_FUNCTION_INFO_V1(pgl_epoint_lon); +Datum pgl_epoint_lon(PG_FUNCTION_ARGS) { + PG_RETURN_FLOAT8(((pgl_point *)PG_GETARG_POINTER(0))->lon); +} + +/* extract minimum latitude from box ("ebox") */ +PG_FUNCTION_INFO_V1(pgl_ebox_lat_min); +Datum pgl_ebox_lat_min(PG_FUNCTION_ARGS) { + PG_RETURN_FLOAT8(((pgl_box *)PG_GETARG_POINTER(0))->lat_min); +} + +/* extract maximum latitude from box ("ebox") */ +PG_FUNCTION_INFO_V1(pgl_ebox_lat_max); +Datum pgl_ebox_lat_max(PG_FUNCTION_ARGS) { + PG_RETURN_FLOAT8(((pgl_box *)PG_GETARG_POINTER(0))->lat_max); +} + +/* extract minimum longitude from box ("ebox") */ +PG_FUNCTION_INFO_V1(pgl_ebox_lon_min); +Datum pgl_ebox_lon_min(PG_FUNCTION_ARGS) { + PG_RETURN_FLOAT8(((pgl_box *)PG_GETARG_POINTER(0))->lon_min); +} + +/* extract maximum longitude from box ("ebox") */ +PG_FUNCTION_INFO_V1(pgl_ebox_lon_max); +Datum pgl_ebox_lon_max(PG_FUNCTION_ARGS) { + PG_RETURN_FLOAT8(((pgl_box *)PG_GETARG_POINTER(0))->lon_max); +} + +/* extract center point from circle ("ecircle") */ +PG_FUNCTION_INFO_V1(pgl_ecircle_center); +Datum pgl_ecircle_center(PG_FUNCTION_ARGS) { + PG_RETURN_POINTER(&(((pgl_circle *)PG_GETARG_POINTER(0))->center)); +} + +/* extract radius from circle ("ecircle") */ +PG_FUNCTION_INFO_V1(pgl_ecircle_radius); +Datum pgl_ecircle_radius(PG_FUNCTION_ARGS) { + PG_RETURN_FLOAT8(((pgl_circle *)PG_GETARG_POINTER(0))->radius); +} + +/* check if point is inside box (overlap operator "&&") in SQL */ +PG_FUNCTION_INFO_V1(pgl_epoint_ebox_overlap); +Datum pgl_epoint_ebox_overlap(PG_FUNCTION_ARGS) { + pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); + pgl_box *box = (pgl_box *)PG_GETARG_POINTER(1); + PG_RETURN_BOOL(pgl_point_in_box(point, box)); +} + +/* check if point is inside circle (overlap operator "&&") in SQL */ +PG_FUNCTION_INFO_V1(pgl_epoint_ecircle_overlap); +Datum pgl_epoint_ecircle_overlap(PG_FUNCTION_ARGS) { + pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); + pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(1); + PG_RETURN_BOOL( + pgl_distance( + point->lat, point->lon, + circle->center.lat, circle->center.lon + ) <= circle->radius + ); +} + +/* check if point is inside cluster (overlap operator "&&") in SQL */ +PG_FUNCTION_INFO_V1(pgl_epoint_ecluster_overlap); +Datum pgl_epoint_ecluster_overlap(PG_FUNCTION_ARGS) { + pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); + pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + bool retval; + /* points outside bounding circle are always assumed to be non-overlapping + (necessary for consistent table and index scans) */ + if ( + pgl_distance( + point->lat, point->lon, + cluster->bounding.center.lat, cluster->bounding.center.lon + ) > cluster->bounding.radius + ) retval = false; + else retval = pgl_point_in_cluster(point, cluster, false); + PG_FREE_IF_COPY(cluster, 1); + PG_RETURN_BOOL(retval); +} + +/* check if point may be inside cluster (lossy overl. operator "&&+") in SQL */ +PG_FUNCTION_INFO_V1(pgl_epoint_ecluster_may_overlap); +Datum pgl_epoint_ecluster_may_overlap(PG_FUNCTION_ARGS) { + pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); + pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + bool retval = pgl_distance( + point->lat, point->lon, + cluster->bounding.center.lat, cluster->bounding.center.lon + ) <= cluster->bounding.radius; + PG_FREE_IF_COPY(cluster, 1); + PG_RETURN_BOOL(retval); +} + +/* check if two boxes overlap (overlap operator "&&") in SQL */ +PG_FUNCTION_INFO_V1(pgl_ebox_overlap); +Datum pgl_ebox_overlap(PG_FUNCTION_ARGS) { + pgl_box *box1 = (pgl_box *)PG_GETARG_POINTER(0); + pgl_box *box2 = (pgl_box *)PG_GETARG_POINTER(1); + PG_RETURN_BOOL(pgl_boxes_overlap(box1, box2)); +} + +/* check if box and circle may overlap (lossy overl. operator "&&+") in SQL */ +PG_FUNCTION_INFO_V1(pgl_ebox_ecircle_may_overlap); +Datum pgl_ebox_ecircle_may_overlap(PG_FUNCTION_ARGS) { + pgl_box *box = (pgl_box *)PG_GETARG_POINTER(0); + pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(1); + PG_RETURN_BOOL( + pgl_estimate_point_box_distance(&circle->center, box) <= circle->radius + ); +} + +/* check if box and cluster may overlap (lossy overl. operator "&&+") in SQL */ +PG_FUNCTION_INFO_V1(pgl_ebox_ecluster_may_overlap); +Datum pgl_ebox_ecluster_may_overlap(PG_FUNCTION_ARGS) { + pgl_box *box = (pgl_box *)PG_GETARG_POINTER(0); + pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + bool retval = pgl_estimate_point_box_distance( + &cluster->bounding.center, + box + ) <= cluster->bounding.radius; + PG_FREE_IF_COPY(cluster, 1); + PG_RETURN_BOOL(retval); +} + +/* check if two circles overlap (overlap operator "&&") in SQL */ +PG_FUNCTION_INFO_V1(pgl_ecircle_overlap); +Datum pgl_ecircle_overlap(PG_FUNCTION_ARGS) { + pgl_circle *circle1 = (pgl_circle *)PG_GETARG_POINTER(0); + pgl_circle *circle2 = (pgl_circle *)PG_GETARG_POINTER(1); + PG_RETURN_BOOL( + pgl_distance( + circle1->center.lat, circle1->center.lon, + circle2->center.lat, circle2->center.lon + ) <= circle1->radius + circle2->radius + ); +} + +/* check if circle and cluster overlap (overlap operator "&&") in SQL */ +PG_FUNCTION_INFO_V1(pgl_ecircle_ecluster_overlap); +Datum pgl_ecircle_ecluster_overlap(PG_FUNCTION_ARGS) { + pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0); + pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + bool retval = ( + pgl_point_cluster_distance(&(circle->center), cluster) <= circle->radius + ); + PG_FREE_IF_COPY(cluster, 1); + PG_RETURN_BOOL(retval); +} + +/* check if circle and cluster may overlap (l. ov. operator "&&+") in SQL */ +PG_FUNCTION_INFO_V1(pgl_ecircle_ecluster_may_overlap); +Datum pgl_ecircle_ecluster_may_overlap(PG_FUNCTION_ARGS) { + pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0); + pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + bool retval = pgl_distance( + circle->center.lat, circle->center.lon, + cluster->bounding.center.lat, cluster->bounding.center.lon + ) <= circle->radius + cluster->bounding.radius; + PG_FREE_IF_COPY(cluster, 1); + PG_RETURN_BOOL(retval); +} + +/* check if two clusters overlap (overlap operator "&&") in SQL */ +PG_FUNCTION_INFO_V1(pgl_ecluster_overlap); +Datum pgl_ecluster_overlap(PG_FUNCTION_ARGS) { + pgl_cluster *cluster1 = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + pgl_cluster *cluster2 = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + bool retval; + /* clusters with non-touching bounding circles are always assumed to be + non-overlapping (improves performance and is necessary for consistent + table and index scans) */ + if ( + pgl_distance( + cluster1->bounding.center.lat, cluster1->bounding.center.lon, + cluster2->bounding.center.lat, cluster2->bounding.center.lon + ) > cluster1->bounding.radius + cluster2->bounding.radius + ) retval = false; + else retval = pgl_clusters_overlap(cluster1, cluster2); + PG_FREE_IF_COPY(cluster1, 0); + PG_FREE_IF_COPY(cluster2, 1); + PG_RETURN_BOOL(retval); +} + +/* check if two clusters may overlap (lossy overlap operator "&&+") in SQL */ +PG_FUNCTION_INFO_V1(pgl_ecluster_may_overlap); +Datum pgl_ecluster_may_overlap(PG_FUNCTION_ARGS) { + pgl_cluster *cluster1 = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + pgl_cluster *cluster2 = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + bool retval = pgl_distance( + cluster1->bounding.center.lat, cluster1->bounding.center.lon, + cluster2->bounding.center.lat, cluster2->bounding.center.lon + ) <= cluster1->bounding.radius + cluster2->bounding.radius; + PG_FREE_IF_COPY(cluster1, 0); + PG_FREE_IF_COPY(cluster2, 1); + PG_RETURN_BOOL(retval); +} + +/* check if second cluster is in first cluster (cont. operator "@>) in SQL */ +PG_FUNCTION_INFO_V1(pgl_ecluster_contains); +Datum pgl_ecluster_contains(PG_FUNCTION_ARGS) { + pgl_cluster *outer = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + pgl_cluster *inner = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + bool retval; + /* clusters with non-touching bounding circles are always assumed to be + non-overlapping (improves performance and is necessary for consistent + table and index scans) */ + if ( + pgl_distance( + outer->bounding.center.lat, outer->bounding.center.lon, + inner->bounding.center.lat, inner->bounding.center.lon + ) > outer->bounding.radius + inner->bounding.radius + ) retval = false; + else retval = pgl_cluster_in_cluster(outer, inner); + PG_FREE_IF_COPY(outer, 0); + PG_FREE_IF_COPY(inner, 1); + PG_RETURN_BOOL(retval); +} + +/* calculate distance between two points ("<->" operator) in SQL */ +PG_FUNCTION_INFO_V1(pgl_epoint_distance); +Datum pgl_epoint_distance(PG_FUNCTION_ARGS) { + pgl_point *point1 = (pgl_point *)PG_GETARG_POINTER(0); + pgl_point *point2 = (pgl_point *)PG_GETARG_POINTER(1); + PG_RETURN_FLOAT8(pgl_distance( + point1->lat, point1->lon, point2->lat, point2->lon + )); +} + +/* calculate point to circle distance ("<->" operator) in SQL */ +PG_FUNCTION_INFO_V1(pgl_epoint_ecircle_distance); +Datum pgl_epoint_ecircle_distance(PG_FUNCTION_ARGS) { + pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); + pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(1); + double distance = pgl_distance( + point->lat, point->lon, circle->center.lat, circle->center.lon + ) - circle->radius; + PG_RETURN_FLOAT8((distance <= 0) ? 0 : distance); +} + +/* calculate point to cluster distance ("<->" operator) in SQL */ +PG_FUNCTION_INFO_V1(pgl_epoint_ecluster_distance); +Datum pgl_epoint_ecluster_distance(PG_FUNCTION_ARGS) { + pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); + pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + double distance = pgl_point_cluster_distance(point, cluster); + PG_FREE_IF_COPY(cluster, 1); + PG_RETURN_FLOAT8(distance); +} + +/* calculate distance between two circles ("<->" operator) in SQL */ +PG_FUNCTION_INFO_V1(pgl_ecircle_distance); +Datum pgl_ecircle_distance(PG_FUNCTION_ARGS) { + pgl_circle *circle1 = (pgl_circle *)PG_GETARG_POINTER(0); + pgl_circle *circle2 = (pgl_circle *)PG_GETARG_POINTER(1); + double distance = pgl_distance( + circle1->center.lat, circle1->center.lon, + circle2->center.lat, circle2->center.lon + ) - (circle1->radius + circle2->radius); + PG_RETURN_FLOAT8((distance <= 0) ? 0 : distance); +} + +/* calculate circle to cluster distance ("<->" operator) in SQL */ +PG_FUNCTION_INFO_V1(pgl_ecircle_ecluster_distance); +Datum pgl_ecircle_ecluster_distance(PG_FUNCTION_ARGS) { + pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0); + pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + double distance = ( + pgl_point_cluster_distance(&(circle->center), cluster) - circle->radius + ); + PG_FREE_IF_COPY(cluster, 1); + PG_RETURN_FLOAT8((distance <= 0) ? 0 : distance); +} + +/* calculate distance between two clusters ("<->" operator) in SQL */ +PG_FUNCTION_INFO_V1(pgl_ecluster_distance); +Datum pgl_ecluster_distance(PG_FUNCTION_ARGS) { + pgl_cluster *cluster1 = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + pgl_cluster *cluster2 = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + double retval = pgl_cluster_distance(cluster1, cluster2); + PG_FREE_IF_COPY(cluster1, 0); + PG_FREE_IF_COPY(cluster2, 1); + PG_RETURN_FLOAT8(retval); +} + +PG_FUNCTION_INFO_V1(pgl_ecluster_monte_carlo_area); +Datum pgl_ecluster_monte_carlo_area(PG_FUNCTION_ARGS) { + pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + int samples = PG_GETARG_INT32(1); + double retval = pgl_monte_carlo_area(cluster, samples); + PG_FREE_IF_COPY(cluster, 0); + PG_RETURN_FLOAT8(retval); +} + +PG_FUNCTION_INFO_V1(pgl_ecluster_epoint_fair_distance); +Datum pgl_ecluster_epoint_fair_distance(PG_FUNCTION_ARGS) { + pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + pgl_point *point = (pgl_point *)PG_GETARG_POINTER(1); + int samples = PG_GETARG_INT32(2); + double retval = pgl_fair_distance(point, cluster, samples); + PG_FREE_IF_COPY(cluster, 0); + PG_RETURN_FLOAT8(retval); +} + + +/*-----------------------------------------------------------* + * B-tree comparison operators and index support functions * + *-----------------------------------------------------------*/ + +/* macro for a B-tree operator (without detoasting) */ +#define PGL_BTREE_OPER(func, type, cmpfunc, oper) \ + PG_FUNCTION_INFO_V1(func); \ + Datum func(PG_FUNCTION_ARGS) { \ + type *a = (type *)PG_GETARG_POINTER(0); \ + type *b = (type *)PG_GETARG_POINTER(1); \ + PG_RETURN_BOOL(cmpfunc(a, b) oper 0); \ + } + +/* macro for a B-tree comparison function (without detoasting) */ +#define PGL_BTREE_CMP(func, type, cmpfunc) \ + PG_FUNCTION_INFO_V1(func); \ + Datum func(PG_FUNCTION_ARGS) { \ + type *a = (type *)PG_GETARG_POINTER(0); \ + type *b = (type *)PG_GETARG_POINTER(1); \ + PG_RETURN_INT32(cmpfunc(a, b)); \ + } + +/* macro for a B-tree operator (with detoasting) */ +#define PGL_BTREE_OPER_DETOAST(func, type, cmpfunc, oper) \ + PG_FUNCTION_INFO_V1(func); \ + Datum func(PG_FUNCTION_ARGS) { \ + bool res; \ + type *a = (type *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); \ + type *b = (type *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); \ + res = cmpfunc(a, b) oper 0; \ + PG_FREE_IF_COPY(a, 0); \ + PG_FREE_IF_COPY(b, 1); \ + PG_RETURN_BOOL(res); \ + } + +/* macro for a B-tree comparison function (with detoasting) */ +#define PGL_BTREE_CMP_DETOAST(func, type, cmpfunc) \ + PG_FUNCTION_INFO_V1(func); \ + Datum func(PG_FUNCTION_ARGS) { \ + int32_t res; \ + type *a = (type *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); \ + type *b = (type *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); \ + res = cmpfunc(a, b); \ + PG_FREE_IF_COPY(a, 0); \ + PG_FREE_IF_COPY(b, 1); \ + PG_RETURN_INT32(res); \ + } + +/* B-tree operators and comparison function for point */ +PGL_BTREE_OPER(pgl_btree_epoint_lt, pgl_point, pgl_point_cmp, <) +PGL_BTREE_OPER(pgl_btree_epoint_le, pgl_point, pgl_point_cmp, <=) +PGL_BTREE_OPER(pgl_btree_epoint_eq, pgl_point, pgl_point_cmp, ==) +PGL_BTREE_OPER(pgl_btree_epoint_ne, pgl_point, pgl_point_cmp, !=) +PGL_BTREE_OPER(pgl_btree_epoint_ge, pgl_point, pgl_point_cmp, >=) +PGL_BTREE_OPER(pgl_btree_epoint_gt, pgl_point, pgl_point_cmp, >) +PGL_BTREE_CMP(pgl_btree_epoint_cmp, pgl_point, pgl_point_cmp) + +/* B-tree operators and comparison function for box */ +PGL_BTREE_OPER(pgl_btree_ebox_lt, pgl_box, pgl_box_cmp, <) +PGL_BTREE_OPER(pgl_btree_ebox_le, pgl_box, pgl_box_cmp, <=) +PGL_BTREE_OPER(pgl_btree_ebox_eq, pgl_box, pgl_box_cmp, ==) +PGL_BTREE_OPER(pgl_btree_ebox_ne, pgl_box, pgl_box_cmp, !=) +PGL_BTREE_OPER(pgl_btree_ebox_ge, pgl_box, pgl_box_cmp, >=) +PGL_BTREE_OPER(pgl_btree_ebox_gt, pgl_box, pgl_box_cmp, >) +PGL_BTREE_CMP(pgl_btree_ebox_cmp, pgl_box, pgl_box_cmp) + +/* B-tree operators and comparison function for circle */ +PGL_BTREE_OPER(pgl_btree_ecircle_lt, pgl_circle, pgl_circle_cmp, <) +PGL_BTREE_OPER(pgl_btree_ecircle_le, pgl_circle, pgl_circle_cmp, <=) +PGL_BTREE_OPER(pgl_btree_ecircle_eq, pgl_circle, pgl_circle_cmp, ==) +PGL_BTREE_OPER(pgl_btree_ecircle_ne, pgl_circle, pgl_circle_cmp, !=) +PGL_BTREE_OPER(pgl_btree_ecircle_ge, pgl_circle, pgl_circle_cmp, >=) +PGL_BTREE_OPER(pgl_btree_ecircle_gt, pgl_circle, pgl_circle_cmp, >) +PGL_BTREE_CMP(pgl_btree_ecircle_cmp, pgl_circle, pgl_circle_cmp) + + +/*--------------------------------* + * GiST index support functions * + *--------------------------------*/ + +/* GiST "consistent" support function */ +PG_FUNCTION_INFO_V1(pgl_gist_consistent); +Datum pgl_gist_consistent(PG_FUNCTION_ARGS) { + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + pgl_keyptr key = (pgl_keyptr)DatumGetPointer(entry->key); + StrategyNumber strategy = (StrategyNumber)PG_GETARG_UINT16(2); + bool *recheck = (bool *)PG_GETARG_POINTER(4); + /* demand recheck because index and query methods are lossy */ + *recheck = true; + /* strategy number aliases for different operators using the same strategy */ + strategy %= 100; + /* strategy number 11: equality of two points */ + if (strategy == 11) { + /* query datum is another point */ + pgl_point *query = (pgl_point *)PG_GETARG_POINTER(1); + /* convert other point to key */ + pgl_pointkey querykey; + pgl_point_to_key(query, querykey); + /* return true if both keys overlap */ + PG_RETURN_BOOL(pgl_keys_overlap(key, querykey)); + } + /* strategy number 13: equality of two circles */ + if (strategy == 13) { + /* query datum is another circle */ + pgl_circle *query = (pgl_circle *)PG_GETARG_POINTER(1); + /* convert other circle to key */ + pgl_areakey querykey; + pgl_circle_to_key(query, querykey); + /* return true if both keys overlap */ + PG_RETURN_BOOL(pgl_keys_overlap(key, querykey)); + } + /* for all remaining strategies, keys on empty objects produce no match */ + /* (check necessary because query radius may be infinite) */ + if (PGL_KEY_IS_EMPTY(key)) PG_RETURN_BOOL(false); + /* strategy number 21: overlapping with point */ + if (strategy == 21) { + /* query datum is a point */ + pgl_point *query = (pgl_point *)PG_GETARG_POINTER(1); + /* return true if estimated distance (allowed to be smaller than real + distance) between index key and point is zero */ + PG_RETURN_BOOL(pgl_estimate_key_distance(key, query) == 0); + } + /* strategy number 22: (point) overlapping with box */ + if (strategy == 22) { + /* query datum is a box */ + pgl_box *query = (pgl_box *)PG_GETARG_POINTER(1); + /* determine bounding box of indexed key */ + pgl_box keybox; + pgl_key_to_box(key, &keybox); + /* return true if query box overlaps with bounding box of indexed key */ + PG_RETURN_BOOL(pgl_boxes_overlap(query, &keybox)); + } + /* strategy number 23: overlapping with circle */ + if (strategy == 23) { + /* query datum is a circle */ + pgl_circle *query = (pgl_circle *)PG_GETARG_POINTER(1); + /* return true if estimated distance (allowed to be smaller than real + distance) between index key and circle center is smaller than radius */ + PG_RETURN_BOOL( + pgl_estimate_key_distance(key, &(query->center)) <= query->radius + ); + } + /* strategy number 24: overlapping with cluster */ + if (strategy == 24) { + bool retval; /* return value */ + /* query datum is a cluster */ + pgl_cluster *query = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + /* return true if estimated distance (allowed to be smaller than real + distance) between index key and circle center is smaller than radius */ + retval = ( + pgl_estimate_key_distance(key, &(query->bounding.center)) <= + query->bounding.radius + ); + PG_FREE_IF_COPY(query, 1); /* free detoasted cluster (if copy) */ + PG_RETURN_BOOL(retval); + } + /* throw error for any unknown strategy number */ + elog(ERROR, "unrecognized strategy number: %d", strategy); +} + +/* GiST "union" support function */ +PG_FUNCTION_INFO_V1(pgl_gist_union); +Datum pgl_gist_union(PG_FUNCTION_ARGS) { + GistEntryVector *entryvec = (GistEntryVector *)PG_GETARG_POINTER(0); + pgl_keyptr out; /* return value (to be palloc'ed) */ + int i; + /* determine key size */ + size_t keysize = PGL_KEY_IS_AREAKEY( + (pgl_keyptr)DatumGetPointer(entryvec->vector[0].key) + ) ? sizeof (pgl_areakey) : sizeof(pgl_pointkey); + /* begin with first key as result */ + out = palloc(keysize); + memcpy(out, (pgl_keyptr)DatumGetPointer(entryvec->vector[0].key), keysize); + /* unite current result with second, third, etc. key */ + for (i=1; in; i++) { + pgl_unite_keys(out, (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key)); + } + /* return result */ + PG_RETURN_POINTER(out); +} + +/* GiST "compress" support function for indicis on points */ +PG_FUNCTION_INFO_V1(pgl_gist_compress_epoint); +Datum pgl_gist_compress_epoint(PG_FUNCTION_ARGS) { + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval; /* return value (to be palloc'ed unless set to entry) */ + /* only transform new leaves */ + if (entry->leafkey) { + /* get point to be transformed */ + pgl_point *point = (pgl_point *)DatumGetPointer(entry->key); + /* allocate memory for key */ + pgl_keyptr key = palloc(sizeof(pgl_pointkey)); + /* transform point to key */ + pgl_point_to_key(point, key); + /* create new GISTENTRY structure as return value */ + retval = palloc(sizeof(GISTENTRY)); + gistentryinit( + *retval, PointerGetDatum(key), + entry->rel, entry->page, entry->offset, FALSE + ); + } else { + /* inner nodes have already been transformed */ + retval = entry; + } + /* return pointer to old or new GISTENTRY structure */ + PG_RETURN_POINTER(retval); +} + +/* GiST "compress" support function for indicis on circles */ +PG_FUNCTION_INFO_V1(pgl_gist_compress_ecircle); +Datum pgl_gist_compress_ecircle(PG_FUNCTION_ARGS) { + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval; /* return value (to be palloc'ed unless set to entry) */ + /* only transform new leaves */ + if (entry->leafkey) { + /* get circle to be transformed */ + pgl_circle *circle = (pgl_circle *)DatumGetPointer(entry->key); + /* allocate memory for key */ + pgl_keyptr key = palloc(sizeof(pgl_areakey)); + /* transform circle to key */ + pgl_circle_to_key(circle, key); + /* create new GISTENTRY structure as return value */ + retval = palloc(sizeof(GISTENTRY)); + gistentryinit( + *retval, PointerGetDatum(key), + entry->rel, entry->page, entry->offset, FALSE + ); + } else { + /* inner nodes have already been transformed */ + retval = entry; + } + /* return pointer to old or new GISTENTRY structure */ + PG_RETURN_POINTER(retval); +} + +/* GiST "compress" support function for indices on clusters */ +PG_FUNCTION_INFO_V1(pgl_gist_compress_ecluster); +Datum pgl_gist_compress_ecluster(PG_FUNCTION_ARGS) { + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval; /* return value (to be palloc'ed unless set to entry) */ + /* only transform new leaves */ + if (entry->leafkey) { + /* get cluster to be transformed (detoasting necessary!) */ + pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(entry->key); + /* allocate memory for key */ + pgl_keyptr key = palloc(sizeof(pgl_areakey)); + /* transform cluster to key */ + pgl_circle_to_key(&(cluster->bounding), key); + /* create new GISTENTRY structure as return value */ + retval = palloc(sizeof(GISTENTRY)); + gistentryinit( + *retval, PointerGetDatum(key), + entry->rel, entry->page, entry->offset, FALSE + ); + /* free detoasted datum */ + if ((void *)cluster != (void *)DatumGetPointer(entry->key)) pfree(cluster); + } else { + /* inner nodes have already been transformed */ + retval = entry; + } + /* return pointer to old or new GISTENTRY structure */ + PG_RETURN_POINTER(retval); +} + +/* GiST "decompress" support function for indices */ +PG_FUNCTION_INFO_V1(pgl_gist_decompress); +Datum pgl_gist_decompress(PG_FUNCTION_ARGS) { + /* return passed pointer without transformation */ + PG_RETURN_POINTER(PG_GETARG_POINTER(0)); +} + +/* GiST "penalty" support function */ +PG_FUNCTION_INFO_V1(pgl_gist_penalty); +Datum pgl_gist_penalty(PG_FUNCTION_ARGS) { + GISTENTRY *origentry = (GISTENTRY *)PG_GETARG_POINTER(0); + GISTENTRY *newentry = (GISTENTRY *)PG_GETARG_POINTER(1); + float *penalty = (float *)PG_GETARG_POINTER(2); + /* get original key and key to insert */ + pgl_keyptr orig = (pgl_keyptr)DatumGetPointer(origentry->key); + pgl_keyptr new = (pgl_keyptr)DatumGetPointer(newentry->key); + /* copy original key */ + union { pgl_pointkey pointkey; pgl_areakey areakey; } union_key; + if (PGL_KEY_IS_AREAKEY(orig)) { + memcpy(union_key.areakey, orig, sizeof(union_key.areakey)); + } else { + memcpy(union_key.pointkey, orig, sizeof(union_key.pointkey)); + } + /* calculate union of both keys */ + pgl_unite_keys((pgl_keyptr)&union_key, new); + /* penalty equal to reduction of key length (logarithm of added area) */ + /* (return value by setting referenced value and returning pointer) */ + *penalty = ( + PGL_KEY_NODEDEPTH(orig) - PGL_KEY_NODEDEPTH((pgl_keyptr)&union_key) + ); + PG_RETURN_POINTER(penalty); +} + +/* GiST "picksplit" support function */ +PG_FUNCTION_INFO_V1(pgl_gist_picksplit); +Datum pgl_gist_picksplit(PG_FUNCTION_ARGS) { + GistEntryVector *entryvec = (GistEntryVector *)PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *)PG_GETARG_POINTER(1); + OffsetNumber i; /* between FirstOffsetNumber and entryvec->n (inclusive) */ + union { + pgl_pointkey pointkey; + pgl_areakey areakey; + } union_all; /* union of all keys (to be calculated from scratch) + (later cut in half) */ + int is_areakey = PGL_KEY_IS_AREAKEY( + (pgl_keyptr)DatumGetPointer(entryvec->vector[FirstOffsetNumber].key) + ); + int keysize = is_areakey ? sizeof(pgl_areakey) : sizeof(pgl_pointkey); + pgl_keyptr unionL = palloc(keysize); /* union of keys that go left */ + pgl_keyptr unionR = palloc(keysize); /* union of keys that go right */ + pgl_keyptr key; /* current key to be processed */ + /* allocate memory for array of left and right keys, set counts to zero */ + v->spl_left = (OffsetNumber *)palloc(entryvec->n * sizeof(OffsetNumber)); + v->spl_nleft = 0; + v->spl_right = (OffsetNumber *)palloc(entryvec->n * sizeof(OffsetNumber)); + v->spl_nright = 0; + /* calculate union of all keys from scratch */ + memcpy( + (pgl_keyptr)&union_all, + (pgl_keyptr)DatumGetPointer(entryvec->vector[FirstOffsetNumber].key), + keysize + ); + for (i=FirstOffsetNumber+1; in; i=OffsetNumberNext(i)) { + pgl_unite_keys( + (pgl_keyptr)&union_all, + (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key) + ); + } + /* check if trivial split is necessary due to exhausted key length */ + /* (Note: keys for empty objects must have node depth set to maximum) */ + if (PGL_KEY_NODEDEPTH((pgl_keyptr)&union_all) == ( + is_areakey ? PGL_AREAKEY_MAXDEPTH : PGL_POINTKEY_MAXDEPTH + )) { + /* half of all keys go left */ + for ( + i=FirstOffsetNumber; + in - FirstOffsetNumber)/2; + i=OffsetNumberNext(i) + ) { + /* pointer to current key */ + key = (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key); + /* update unionL */ + /* check if key is first key that goes left */ + if (!v->spl_nleft) { + /* first key that goes left is just copied to unionL */ + memcpy(unionL, key, keysize); + } else { + /* unite current value and next key */ + pgl_unite_keys(unionL, key); + } + /* append offset number to list of keys that go left */ + v->spl_left[v->spl_nleft++] = i; + } + /* other half goes right */ + for ( + i=FirstOffsetNumber+(entryvec->n - FirstOffsetNumber)/2; + in; + i=OffsetNumberNext(i) + ) { + /* pointer to current key */ + key = (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key); + /* update unionR */ + /* check if key is first key that goes right */ + if (!v->spl_nright) { + /* first key that goes right is just copied to unionR */ + memcpy(unionR, key, keysize); + } else { + /* unite current value and next key */ + pgl_unite_keys(unionR, key); + } + /* append offset number to list of keys that go right */ + v->spl_right[v->spl_nright++] = i; + } + } + /* otherwise, a non-trivial split is possible */ + else { + /* cut covered area in half */ + /* (union_all then refers to area of keys that go left) */ + /* check if union of all keys covers empty and non-empty objects */ + if (PGL_KEY_IS_UNIVERSAL((pgl_keyptr)&union_all)) { + /* if yes, split into empty and non-empty objects */ + pgl_key_set_empty((pgl_keyptr)&union_all); + } else { + /* otherwise split by next bit */ + ((pgl_keyptr)&union_all)[PGL_KEY_NODEDEPTH_OFFSET]++; + /* NOTE: type bit conserved */ + } + /* determine for each key if it goes left or right */ + for (i=FirstOffsetNumber; in; i=OffsetNumberNext(i)) { + /* pointer to current key */ + key = (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key); + /* keys within one half of the area go left */ + if (pgl_keys_overlap((pgl_keyptr)&union_all, key)) { + /* update unionL */ + /* check if key is first key that goes left */ + if (!v->spl_nleft) { + /* first key that goes left is just copied to unionL */ + memcpy(unionL, key, keysize); + } else { + /* unite current value of unionL and processed key */ + pgl_unite_keys(unionL, key); + } + /* append offset number to list of keys that go left */ + v->spl_left[v->spl_nleft++] = i; + } + /* the other keys go right */ + else { + /* update unionR */ + /* check if key is first key that goes right */ + if (!v->spl_nright) { + /* first key that goes right is just copied to unionR */ + memcpy(unionR, key, keysize); + } else { + /* unite current value of unionR and processed key */ + pgl_unite_keys(unionR, key); + } + /* append offset number to list of keys that go right */ + v->spl_right[v->spl_nright++] = i; + } + } + } + /* store unions in return value */ + v->spl_ldatum = PointerGetDatum(unionL); + v->spl_rdatum = PointerGetDatum(unionR); + /* return all results */ + PG_RETURN_POINTER(v); +} + +/* GiST "same"/"equal" support function */ +PG_FUNCTION_INFO_V1(pgl_gist_same); +Datum pgl_gist_same(PG_FUNCTION_ARGS) { + pgl_keyptr key1 = (pgl_keyptr)PG_GETARG_POINTER(0); + pgl_keyptr key2 = (pgl_keyptr)PG_GETARG_POINTER(1); + bool *boolptr = (bool *)PG_GETARG_POINTER(2); + /* two keys are equal if they are binary equal */ + /* (return result by setting referenced boolean and returning pointer) */ + *boolptr = !memcmp( + key1, + key2, + PGL_KEY_IS_AREAKEY(key1) ? sizeof(pgl_areakey) : sizeof(pgl_pointkey) + ); + PG_RETURN_POINTER(boolptr); +} + +/* GiST "distance" support function */ +PG_FUNCTION_INFO_V1(pgl_gist_distance); +Datum pgl_gist_distance(PG_FUNCTION_ARGS) { + GISTENTRY *entry = (GISTENTRY *)PG_GETARG_POINTER(0); + pgl_keyptr key = (pgl_keyptr)DatumGetPointer(entry->key); + StrategyNumber strategy = (StrategyNumber)PG_GETARG_UINT16(2); + bool *recheck = (bool *)PG_GETARG_POINTER(4); + double distance; /* return value */ + /* demand recheck because distance is just an estimation */ + /* (real distance may be bigger) */ + *recheck = true; + /* strategy number aliases for different operators using the same strategy */ + strategy %= 100; + /* strategy number 31: distance to point */ + if (strategy == 31) { + /* query datum is a point */ + pgl_point *query = (pgl_point *)PG_GETARG_POINTER(1); + /* use pgl_estimate_pointkey_distance() function to compute result */ + distance = pgl_estimate_key_distance(key, query); + /* avoid infinity (reserved!) */ + if (!isfinite(distance)) distance = PGL_ULTRA_DISTANCE; + /* return result */ + PG_RETURN_FLOAT8(distance); + } + /* strategy number 33: distance to circle */ + if (strategy == 33) { + /* query datum is a circle */ + pgl_circle *query = (pgl_circle *)PG_GETARG_POINTER(1); + /* estimate distance to circle center and substract circle radius */ + distance = ( + pgl_estimate_key_distance(key, &(query->center)) - query->radius + ); + /* convert non-positive values to zero and avoid infinity (reserved!) */ + if (distance <= 0) distance = 0; + else if (!isfinite(distance)) distance = PGL_ULTRA_DISTANCE; + /* return result */ + PG_RETURN_FLOAT8(distance); + } + /* strategy number 34: distance to cluster */ + if (strategy == 34) { + /* query datum is a cluster */ + pgl_cluster *query = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + /* estimate distance to bounding center and substract bounding radius */ + distance = ( + pgl_estimate_key_distance(key, &(query->bounding.center)) - + query->bounding.radius + ); + /* convert non-positive values to zero and avoid infinity (reserved!) */ + if (distance <= 0) distance = 0; + else if (!isfinite(distance)) distance = PGL_ULTRA_DISTANCE; + /* free detoasted cluster (if copy) */ + PG_FREE_IF_COPY(query, 1); + /* return result */ + PG_RETURN_FLOAT8(distance); + } + /* throw error for any unknown strategy number */ + elog(ERROR, "unrecognized strategy number: %d", strategy); +} + diff -r a90c0d750405 -r 1b9cd45e9e48 latlon.control --- a/latlon.control Fri Oct 21 12:57:46 2016 +0200 +++ b/latlon.control Tue Oct 25 18:44:43 2016 +0200 @@ -1,3 +1,3 @@ comment = 'Geographic data types and spatial indexing for the WGS-84 spheroid' -default_version = '0.8' +default_version = '0.9' relocatable = true