SET LOCAL search_path TO @extschema@;


----------------------------------------
-- forward declarations (shell types) --
----------------------------------------

CREATE TYPE ekey_point;
CREATE TYPE ekey_area;
CREATE TYPE epoint;
CREATE TYPE epoint_with_sample_count;
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-v0010', 'pgl_notimpl';

CREATE FUNCTION ekey_point_out_dummy(ekey_point)
  RETURNS cstring
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_notimpl';

CREATE FUNCTION ekey_area_in_dummy(cstring)
  RETURNS ekey_area
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_notimpl';

CREATE FUNCTION ekey_area_out_dummy(ekey_area)
  RETURNS cstring
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_notimpl';


--------------------------
-- text input functions --
--------------------------

CREATE FUNCTION epoint_in(cstring)
  RETURNS epoint
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_in';

CREATE FUNCTION epoint_with_sample_count_in(cstring)
  RETURNS epoint_with_sample_count
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_with_sample_count_in';

CREATE FUNCTION ebox_in(cstring)
  RETURNS ebox
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ebox_in';

CREATE FUNCTION ecircle_in(cstring)
  RETURNS ecircle
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecircle_in';

CREATE FUNCTION ecluster_in(cstring)
  RETURNS ecluster
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecluster_in';


---------------------------
-- text output functions --
---------------------------

CREATE FUNCTION epoint_out(epoint)
  RETURNS cstring
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_out';

CREATE FUNCTION epoint_with_sample_count_out(epoint_with_sample_count)
  RETURNS cstring
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_with_sample_count_out';

CREATE FUNCTION ebox_out(ebox)
  RETURNS cstring
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ebox_out';

CREATE FUNCTION ecircle_out(ecircle)
  RETURNS cstring
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecircle_out';

CREATE FUNCTION ecluster_out(ecluster)
  RETURNS cstring
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecluster_out';


--------------------------
-- binary I/O functions --
--------------------------

CREATE FUNCTION epoint_recv(internal)
  RETURNS epoint
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_recv';

CREATE FUNCTION ebox_recv(internal)
  RETURNS ebox
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ebox_recv';

CREATE FUNCTION ecircle_recv(internal)
  RETURNS ecircle
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecircle_recv';

CREATE FUNCTION epoint_send(epoint)
  RETURNS bytea
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_send';

CREATE FUNCTION ebox_send(ebox)
  RETURNS bytea
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ebox_send';

CREATE FUNCTION ecircle_send(ecircle)
  RETURNS bytea
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', '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 epoint_with_sample_count (
  internallength = 20,
  input = epoint_with_sample_count_in,
  output = epoint_with_sample_count_out,
  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-v0010', 'pgl_btree_epoint_lt';

CREATE FUNCTION epoint_btree_le(epoint, epoint)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_btree_epoint_le';

CREATE FUNCTION epoint_btree_eq(epoint, epoint)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_btree_epoint_eq';

CREATE FUNCTION epoint_btree_ne(epoint, epoint)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_btree_epoint_ne';

CREATE FUNCTION epoint_btree_ge(epoint, epoint)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_btree_epoint_ge';

CREATE FUNCTION epoint_btree_gt(epoint, epoint)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', '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_ne,
  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-v0010', '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-v0010', 'pgl_btree_ebox_lt';

CREATE FUNCTION ebox_btree_le(ebox, ebox)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_btree_ebox_le';

CREATE FUNCTION ebox_btree_eq(ebox, ebox)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_btree_ebox_eq';

CREATE FUNCTION ebox_btree_ne(ebox, ebox)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_btree_ebox_ne';

CREATE FUNCTION ebox_btree_ge(ebox, ebox)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_btree_ebox_ge';

CREATE FUNCTION ebox_btree_gt(ebox, ebox)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', '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_ne,
  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-v0010', '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-v0010', 'pgl_btree_ecircle_lt';

CREATE FUNCTION ecircle_btree_le(ecircle, ecircle)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_btree_ecircle_le';

CREATE FUNCTION ecircle_btree_eq(ecircle, ecircle)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_btree_ecircle_eq';

CREATE FUNCTION ecircle_btree_ne(ecircle, ecircle)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_btree_ecircle_ne';

CREATE FUNCTION ecircle_btree_ge(ecircle, ecircle)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_btree_ecircle_ge';

CREATE FUNCTION ecircle_btree_gt(ecircle, ecircle)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', '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_ne,
  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-v0010', '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-v0010', '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-v0010', '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-v0010', '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-v0010', '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-v0010', 'pgl_create_epoint';

CREATE FUNCTION epoint_latlon(float8, float8)
  RETURNS epoint
  LANGUAGE SQL IMMUTABLE STRICT AS $$
    SELECT @extschema@.epoint($1, $2)
  $$;

CREATE FUNCTION epoint_lonlat(float8, float8)
  RETURNS epoint
  LANGUAGE SQL IMMUTABLE STRICT AS $$
    SELECT @extschema@.epoint($2, $1)
  $$;

CREATE FUNCTION epoint_with_sample_count(epoint, int4)
  RETURNS epoint_with_sample_count
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_create_epoint_with_sample_count';

CREATE FUNCTION empty_ebox()
  RETURNS ebox
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_create_empty_ebox';

CREATE FUNCTION ebox(float8, float8, float8, float8)
  RETURNS ebox
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_create_ebox';

CREATE FUNCTION ebox(epoint, epoint)
  RETURNS ebox
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_create_ebox_from_epoints';

CREATE FUNCTION ecircle(float8, float8, float8)
  RETURNS ecircle
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_create_ecircle';

CREATE FUNCTION ecircle(epoint, float8)
  RETURNS ecircle
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_create_ecircle_from_epoint';

CREATE FUNCTION ecluster_concat(ecluster[])
  RETURNS ecluster
  LANGUAGE sql IMMUTABLE STRICT AS $$
    SELECT pg_catalog.array_to_string($1, ' ')::@extschema@.ecluster
  $$;

CREATE FUNCTION ecluster_concat(ecluster, ecluster)
  RETURNS ecluster
  LANGUAGE sql IMMUTABLE STRICT AS $$
    SELECT (
      $1::pg_catalog.text OPERATOR(pg_catalog.||) ' ' OPERATOR(pg_catalog.||)
      $2::pg_catalog.text
    )::@extschema@.ecluster
  $$;

CREATE FUNCTION ecluster_create_multipoint(epoint[])
  RETURNS ecluster
  LANGUAGE sql IMMUTABLE STRICT AS $$
    SELECT
      pg_catalog.array_to_string(
        pg_catalog.array_agg(
          'point (' OPERATOR(pg_catalog.||) unnest OPERATOR(pg_catalog.||) ')'
        ),
        ' '
      )::@extschema@.ecluster
    FROM pg_catalog.unnest($1)
  $$;

CREATE FUNCTION ecluster_create_path(epoint[])
  RETURNS ecluster
  LANGUAGE sql IMMUTABLE STRICT AS $$
    SELECT CASE WHEN "str" OPERATOR(pg_catalog.=) '' THEN
      'empty'::@extschema@.ecluster
    ELSE
      (
        'path (' OPERATOR(pg_catalog.||) "str" OPERATOR(pg_catalog.||) ')'
      )::@extschema@.ecluster
    END
    FROM pg_catalog.array_to_string($1, ' ') AS "str"
  $$;

CREATE FUNCTION ecluster_create_outline(epoint[])
  RETURNS ecluster
  LANGUAGE sql IMMUTABLE STRICT AS $$
    SELECT CASE WHEN "str" OPERATOR(pg_catalog.=) '' THEN
      'empty'::@extschema@.ecluster
    ELSE
      (
        'outline (' OPERATOR(pg_catalog.||) "str" OPERATOR(pg_catalog.||) ')'
      )::@extschema@.ecluster
    END
    FROM pg_catalog.array_to_string($1, ' ') AS "str"
  $$;

CREATE FUNCTION ecluster_create_polygon(epoint[])
  RETURNS ecluster
  LANGUAGE sql IMMUTABLE STRICT AS $$
    SELECT CASE WHEN "str" OPERATOR(pg_catalog.=) '' THEN
      'empty'::@extschema@.ecluster
    ELSE
      (
        'polygon (' OPERATOR(pg_catalog.||) pg_catalog.array_to_string($1, ' ')
        OPERATOR(pg_catalog.||) ')'
      )::@extschema@.ecluster
    END
    FROM pg_catalog.array_to_string($1, ' ') AS "str"
  $$;


----------------------
-- getter functions --
----------------------

CREATE FUNCTION latitude(epoint)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_lat';

CREATE FUNCTION longitude(epoint)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_lon';

CREATE FUNCTION min_latitude(ebox)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ebox_lat_min';

CREATE FUNCTION max_latitude(ebox)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ebox_lat_max';

CREATE FUNCTION min_longitude(ebox)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ebox_lon_min';

CREATE FUNCTION max_longitude(ebox)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ebox_lon_max';

CREATE FUNCTION center(ecircle)
  RETURNS epoint
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecircle_center';

CREATE FUNCTION radius(ecircle)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecircle_radius';

CREATE FUNCTION ecluster_extract_points(ecluster)
  RETURNS SETOF epoint
  LANGUAGE sql IMMUTABLE STRICT AS $$
    SELECT "match"[2]::@extschema@.epoint
    FROM pg_catalog.regexp_matches(
      $1::pg_catalog.text, e'(^| )point \\(([^)]+)\\)', 'g'
    ) AS "match"
  $$;

CREATE FUNCTION ecluster_extract_paths(ecluster)
  RETURNS SETOF epoint[]
  LANGUAGE sql IMMUTABLE STRICT AS $$
    SELECT (
      SELECT pg_catalog.array_agg("m2"[1]::@extschema@.epoint)
      FROM pg_catalog.regexp_matches("m1"[2], e'[^ ]+ [^ ]+', 'g') AS "m2"
    )
    FROM pg_catalog.regexp_matches(
      $1::pg_catalog.text, e'(^| )path \\(([^)]+)\\)', 'g'
    ) AS "m1"
  $$;

CREATE FUNCTION ecluster_extract_outlines(ecluster)
  RETURNS SETOF epoint[]
  LANGUAGE sql IMMUTABLE STRICT AS $$
    SELECT (
      SELECT pg_catalog.array_agg("m2"[1]::@extschema@.epoint)
      FROM pg_catalog.regexp_matches("m1"[2], e'[^ ]+ [^ ]+', 'g') AS "m2"
    )
    FROM pg_catalog.regexp_matches(
      $1::pg_catalog.text, e'(^| )outline \\(([^)]+)\\)', 'g'
    ) AS "m1"
  $$;

CREATE FUNCTION ecluster_extract_polygons(ecluster)
  RETURNS SETOF epoint[]
  LANGUAGE sql IMMUTABLE STRICT AS $$
    SELECT (
      SELECT pg_catalog.array_agg("m2"[1]::@extschema@.epoint)
      FROM pg_catalog.regexp_matches("m1"[2], e'[^ ]+ [^ ]+', 'g') AS "m2"
    )
    FROM pg_catalog.regexp_matches(
      $1::pg_catalog.text, e'(^| )polygon \\(([^)]+)\\)', 'g'
    ) AS "m1"
  $$;


---------------
-- operators --
---------------

CREATE FUNCTION epoint_ebox_overlap_proc(epoint, ebox)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_ebox_overlap';

CREATE FUNCTION epoint_ecircle_overlap_proc(epoint, ecircle)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_ecircle_overlap';

CREATE FUNCTION epoint_ecluster_overlap_proc(epoint, ecluster)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_ecluster_overlap';

CREATE FUNCTION epoint_ecluster_may_overlap_proc(epoint, ecluster)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_ecluster_may_overlap';

CREATE FUNCTION ebox_overlap_proc(ebox, ebox)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ebox_overlap';

CREATE FUNCTION ebox_ecircle_may_overlap_proc(ebox, ecircle)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ebox_ecircle_may_overlap';

CREATE FUNCTION ebox_ecluster_may_overlap_proc(ebox, ecluster)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ebox_ecluster_may_overlap';

CREATE FUNCTION ecircle_overlap_proc(ecircle, ecircle)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecircle_overlap';

CREATE FUNCTION ecircle_ecluster_overlap_proc(ecircle, ecluster)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecircle_ecluster_overlap';

CREATE FUNCTION ecircle_ecluster_may_overlap_proc(ecircle, ecluster)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecircle_ecluster_may_overlap';

CREATE FUNCTION ecluster_overlap_proc(ecluster, ecluster)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecluster_overlap';

CREATE FUNCTION ecluster_may_overlap_proc(ecluster, ecluster)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecluster_may_overlap';

CREATE FUNCTION ecluster_contains_proc(ecluster, ecluster)
  RETURNS boolean
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecluster_contains';

CREATE FUNCTION epoint_distance_proc(epoint, epoint)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_distance';

CREATE FUNCTION epoint_ecircle_distance_proc(epoint, ecircle)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_ecircle_distance';

CREATE FUNCTION epoint_ecluster_distance_proc(epoint, ecluster)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_epoint_ecluster_distance';

CREATE FUNCTION ecircle_distance_proc(ecircle, ecircle)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecircle_distance';

CREATE FUNCTION ecircle_ecluster_distance_proc(ecircle, ecluster)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecircle_ecluster_distance';

CREATE FUNCTION ecluster_distance_proc(ecluster, ecluster)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecluster_distance';

CREATE FUNCTION fair_distance_operator_proc(ecluster, epoint_with_sample_count)
  RETURNS float8
  LANGUAGE C IMMUTABLE STRICT
  AS '$libdir/latlon-v0010', 'pgl_ecluster_epoint_sc_fair_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 OPERATOR(@extschema@.&&) $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 OPERATOR(@extschema@.&&) $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 OPERATOR(@extschema@.&&) $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 OPERATOR(@extschema@.&&) $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::@extschema@.ecluster OPERATOR(@extschema@.&&) $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 OPERATOR(@extschema@.&&) $2::@extschema@.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::@extschema@.ecluster OPERATOR(@extschema@.&&) $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 OPERATOR(@extschema@.&&) $2::@extschema@.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 OPERATOR(@extschema@.&&+) $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 OPERATOR(@extschema@.&&+) $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 OPERATOR(@extschema@.&&+) $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 OPERATOR(@extschema@.&&+) $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 OPERATOR(@extschema@.@>) $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::@extschema@.ecluster OPERATOR(@extschema@.@>) $2::@extschema@.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::@extschema@.ecluster OPERATOR(@extschema@.@>) $1::@extschema@.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::@extschema@.ecluster OPERATOR(@extschema@.@>) $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::@extschema@.ecluster OPERATOR(@extschema@.@>) $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 OPERATOR(@extschema@.@>) $2::@extschema@.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 OPERATOR(@extschema@.@>) $1::@extschema@.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 OPERATOR(@extschema@.<->) $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 OPERATOR(@extschema@.<->) $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 OPERATOR(@extschema@.<->) $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 OPERATOR(@extschema@.<->) $2::@extschema@.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::@extschema@.ecluster OPERATOR(@extschema@.<->) $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::@extschema@.ecluster OPERATOR(@extschema@.<->) $2::@extschema@.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::@extschema@.ecluster OPERATOR(@extschema@.<->) $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 OPERATOR(@extschema@.<->) $2::@extschema@.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::@extschema@.ecluster OPERATOR(@extschema@.<->) $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 OPERATOR(@extschema@.<->) $2::@extschema@.ecluster';

CREATE OPERATOR <-> (
  leftarg = ecluster,
  rightarg = ebox,
  procedure = ebox_ecluster_distance_castwrap,
  commutator = <->
);

CREATE OPERATOR <=> (
  leftarg = ecluster,
  rightarg = epoint_with_sample_count,
  procedure = fair_distance_operator_proc
);


----------------
-- GiST index --
----------------

CREATE FUNCTION pgl_gist_consistent(internal, internal, smallint, oid, internal)
  RETURNS boolean
  LANGUAGE C STRICT
  AS '$libdir/latlon-v0010', 'pgl_gist_consistent';

CREATE FUNCTION pgl_gist_union(internal, internal)
  RETURNS internal
  LANGUAGE C STRICT
  AS '$libdir/latlon-v0010', 'pgl_gist_union';

CREATE FUNCTION pgl_gist_compress_epoint(internal)
  RETURNS internal
  LANGUAGE C STRICT
  AS '$libdir/latlon-v0010', 'pgl_gist_compress_epoint';

CREATE FUNCTION pgl_gist_compress_ecircle(internal)
  RETURNS internal
  LANGUAGE C STRICT
  AS '$libdir/latlon-v0010', 'pgl_gist_compress_ecircle';

CREATE FUNCTION pgl_gist_compress_ecluster(internal)
  RETURNS internal
  LANGUAGE C STRICT
  AS '$libdir/latlon-v0010', 'pgl_gist_compress_ecluster';

CREATE FUNCTION pgl_gist_decompress(internal)
  RETURNS internal
  LANGUAGE C STRICT
  AS '$libdir/latlon-v0010', 'pgl_gist_decompress';

CREATE FUNCTION pgl_gist_penalty(internal, internal, internal)
  RETURNS internal
  LANGUAGE C STRICT
  AS '$libdir/latlon-v0010', 'pgl_gist_penalty';

CREATE FUNCTION pgl_gist_picksplit(internal, internal)
  RETURNS internal
  LANGUAGE C STRICT
  AS '$libdir/latlon-v0010', 'pgl_gist_picksplit';

CREATE FUNCTION pgl_gist_same(internal, internal, internal)
  RETURNS internal
  LANGUAGE C STRICT
  AS '$libdir/latlon-v0010', 'pgl_gist_same';

CREATE FUNCTION pgl_gist_distance(internal, internal, smallint, oid)
  RETURNS internal
  LANGUAGE C STRICT
  AS '$libdir/latlon-v0010', '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,
  OPERATOR 131 <=> (ecluster, epoint_with_sample_count) 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 OPERATOR(@extschema@.<->) $2';

CREATE FUNCTION distance(ecluster, epoint)
  RETURNS float8
  LANGUAGE sql IMMUTABLE AS 'SELECT $1 OPERATOR(@extschema@.<->) $2';

CREATE FUNCTION distance_within(epoint, epoint, float8)
  RETURNS boolean
  LANGUAGE sql IMMUTABLE
  AS 'SELECT $1 OPERATOR(@extschema@.&&) @extschema@.ecircle($2, $3)';

CREATE FUNCTION distance_within(ecluster, epoint, float8)
  RETURNS boolean
  LANGUAGE sql IMMUTABLE
  AS 'SELECT $1 OPERATOR(@extschema@.&&) @extschema@.ecircle($2, $3)';

CREATE FUNCTION fair_distance(ecluster, epoint, int4 = 10000)
  RETURNS float8
  LANGUAGE sql IMMUTABLE AS $$
    SELECT
    $1 OPERATOR(@extschema@.<=>) @extschema@.epoint_with_sample_count($2, $3)
  $$;


--------------------------------
-- other data storage formats --
--------------------------------

CREATE FUNCTION coords_to_epoint(float8, float8, text = 'epoint')
  RETURNS epoint
  LANGUAGE plpgsql IMMUTABLE STRICT AS $$
    DECLARE
      "result" @extschema@.epoint;
    BEGIN
      IF $3 OPERATOR(pg_catalog.=) 'epoint_lonlat' THEN
        -- avoid dynamic command execution for better performance
        RETURN @extschema@.epoint($2, $1);
      END IF;
      IF
        $3 OPERATOR(pg_catalog.=) 'epoint' OR
        $3 OPERATOR(pg_catalog.=) 'epoint_latlon'
      THEN
        -- avoid dynamic command execution for better performance
        RETURN @extschema@.epoint($1, $2);
      END IF;
      EXECUTE
        'SELECT ' OPERATOR(pg_catalog.||) $3 OPERATOR(pg_catalog.||) '($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 pg_catalog.jsonb_array_length($1) - 1 )
      AS "lastindex_row" ("lastindex")
      CROSS JOIN LATERAL pg_catalog.jsonb_array_elements(
        CASE WHEN
          @extschema@.coords_to_epoint(
            ($1 OPERATOR(pg_catalog.->) 0 OPERATOR(pg_catalog.->>) 0)
            ::pg_catalog.float8,
            ($1 OPERATOR(pg_catalog.->) 0 OPERATOR(pg_catalog.->>) 1)
            ::pg_catalog.float8,
            $2
          ) OPERATOR(pg_catalog.=) @extschema@.coords_to_epoint(
            ($1 OPERATOR(pg_catalog.->) "lastindex" OPERATOR(pg_catalog.->>) 0)
            ::pg_catalog.float8,
            ($1 OPERATOR(pg_catalog.->) "lastindex" OPERATOR(pg_catalog.->>) 1)
            ::pg_catalog.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 OPERATOR(pg_catalog.->>) 'type' OPERATOR(pg_catalog.=) 'Point' THEN
      @extschema@.coords_to_epoint(
        ($1 OPERATOR(pg_catalog.->) 'coordinates' OPERATOR(pg_catalog.->>) 0)
        ::pg_catalog.float8,
        ($1 OPERATOR(pg_catalog.->) 'coordinates' OPERATOR(pg_catalog.->>) 1)
        ::pg_catalog.float8,
        $2
      )
    WHEN $1->>'type' = 'Feature' THEN
      @extschema@.GeoJSON_to_epoint($1 OPERATOR(pg_catalog.->) 'geometry', $2)
    ELSE
      NULL
    END
  $$;

CREATE FUNCTION GeoJSON_to_ecluster(jsonb, text = 'epoint_lonlat')
  RETURNS ecluster
  LANGUAGE plpgsql IMMUTABLE STRICT AS $$
    DECLARE
      "tp" TEXT = $1 OPERATOR(pg_catalog.->>) 'type';
    BEGIN
      IF "tp" = 'Point' THEN RETURN
        @extschema@.coords_to_epoint(
          ($1 OPERATOR(pg_catalog.->) 'coordinates' OPERATOR(pg_catalog.->>) 0)
          ::pg_catalog.float8,
          ($1 OPERATOR(pg_catalog.->) 'coordinates' OPERATOR(pg_catalog.->>) 1)
          ::pg_catalog.float8,
          $2
        )::@extschema@.ecluster;
      END IF;
      raise notice 'DEBUG2';
      IF "tp" = 'MultiPoint' THEN RETURN
        ( SELECT @extschema@.ecluster_create_multipoint(pg_catalog.array_agg(
            @extschema@.coords_to_epoint(
              ("coord" OPERATOR(pg_catalog.->>) 0)::pg_catalog.float8,
              ("coord" OPERATOR(pg_catalog.->>) 1)::pg_catalog.float8,
              $2
            )
          ))
          FROM pg_catalog.jsonb_array_elements(
            $1 OPERATOR(pg_catalog.->) 'coordinates'
          ) AS "coord"
        );
      END IF;
      IF "tp" = 'LineString' THEN RETURN
        ( SELECT @extschema@.ecluster_create_path(pg_catalog.array_agg(
            @extschema@.coords_to_epoint(
              ("coord" OPERATOR(pg_catalog.->>) 0)::pg_catalog.float8,
              ("coord" OPERATOR(pg_catalog.->>) 1)::pg_catalog.float8,
              $2
            )
          ))
          FROM pg_catalog.jsonb_array_elements(
            $1 OPERATOR(pg_catalog.->) 'coordinates'
          ) AS "coord"
        );
      END IF;
      IF "tp" = 'MultiLineString' THEN RETURN
        ( SELECT @extschema@.ecluster_concat(pg_catalog.array_agg(
            ( SELECT @extschema@.ecluster_create_path(pg_catalog.array_agg(
                @extschema@.coords_to_epoint(
                  ("coord" OPERATOR(pg_catalog.->>) 0)::pg_catalog.float8,
                  ("coord" OPERATOR(pg_catalog.->>) 1)::pg_catalog.float8,
                  $2
                )
              ))
              FROM pg_catalog.jsonb_array_elements("coord_array") AS "coord"
            )
          ))
          FROM pg_catalog.jsonb_array_elements(
            $1 OPERATOR(pg_catalog.->) 'coordinates'
          ) AS "coord_array"
        );
      END IF;
      IF "tp" = 'Polygon' THEN RETURN
        ( SELECT @extschema@.ecluster_concat(pg_catalog.array_agg(
            ( SELECT @extschema@.ecluster_create_polygon(pg_catalog.array_agg(
                @extschema@.coords_to_epoint(
                  ("coord" OPERATOR(pg_catalog.->>) 0)::pg_catalog.float8,
                  ("coord" OPERATOR(pg_catalog.->>) 1)::pg_catalog.float8,
                  $2
                )
              ))
              FROM @extschema@.GeoJSON_LinearRing_vertices("coord_array", $2)
              AS "coord"
            )
          ))
          FROM pg_catalog.jsonb_array_elements(
            $1 OPERATOR(pg_catalog.->) 'coordinates'
          ) AS "coord_array"
        );
      END IF;
      IF "tp" = 'MultiPolygon' THEN RETURN
        ( SELECT @extschema@.ecluster_concat(pg_catalog.array_agg(
            ( SELECT @extschema@.ecluster_concat(pg_catalog.array_agg(
                ( SELECT @extschema@.ecluster_create_polygon(
                    pg_catalog.array_agg(
                      @extschema@.coords_to_epoint(
                        ("coord" OPERATOR(pg_catalog.->>) 0)
                        ::pg_catalog.float8,
                        ("coord" OPERATOR(pg_catalog.->>) 1)
                        ::pg_catalog.float8,
                        $2
                      )
                    )
                  )
                  FROM @extschema@.GeoJSON_LinearRing_vertices(
                    "coord_array", $2
                  ) AS "coord"
                )
              ))
              FROM pg_catalog.jsonb_array_elements("coord_array_array")
              AS "coord_array"
            )
          ))
          FROM jsonb_array_elements(
            $1 OPERATOR(pg_catalog.->) 'coordinates'
          ) AS "coord_array_array"
        );
      END IF;
      IF "tp" = 'GeometryCollection' THEN RETURN
        ( SELECT @extschema@.ecluster_concat(pg_catalog.array_agg(
            @extschema@.GeoJSON_to_ecluster("geometry", $2)
          ))
          FROM pg_catalog.jsonb_array_elements(
            $1 OPERATOR(pg_catalog.->) 'geometries'
          ) AS "geometry"
        );
      END IF;
      IF "tp" = 'Feature' THEN RETURN
        @extschema@.GeoJSON_to_ecluster(
          $1 OPERATOR(pg_catalog.->) 'geometry', $2
        );
      END IF;
      IF "tp" = 'FeatureCollection' THEN RETURN
        ( SELECT @extschema@.ecluster_concat(pg_catalog.array_agg(
            @extschema@.GeoJSON_to_ecluster("feature", $2)
          ))
          FROM pg_catalog.jsonb_array_elements(
            $1 OPERATOR(pg_catalog.->) 'features'
          ) AS "feature"
        );
      END IF;
      RETURN NULL;
    END;
  $$;

