drop type if exists tpv cascade;
-- TPV = time, position, velocity
create type TPV as (
    -- position
    geom     geometry(point, 3857),
    accuracy float,

    -- velocity
    heading  float,
    speed    float,

    -- time
    ts       timestamptz,

    -- helpers
    source   text,
    osm_id   bigint
);

-- convenience functions
create or replace function _tpv_from_json(
    p_tpv jsonb
)
    returns tpv
as $$
select (
    ST_Transform(
        ST_SetSRID(
            ST_MakePoint(
                (p_tpv ->> 'lon') :: float,
                (p_tpv ->> 'lat') :: float
            ),
            4326
        ),
        3857
    ),
    p_tpv ->> 'acc',
    p_tpv ->> 'hdg',
    p_tpv ->> 'spd',
    to_timestamp((p_tpv ->> 'ts') :: float / 1000),
    p_tpv ->> 'src',
    p_tpv ->> 'osm_id'
) :: tpv
$$ language 'sql' immutable strict parallel safe;

drop cast if exists ( jsonb as tpv );
create cast ( jsonb as tpv )
with function _tpv_from_json(jsonb);

create or replace function _tpv_from_geometry(
    p_geom geometry
)
    returns tpv
as $$
select (
    ST_Force2D(
        ST_Transform(
            p_geom,
            3857
        )
    ),
    0,
    0,
    0,
    to_timestamp(0),
    null,
    null
) :: tpv
$$ language 'sql' immutable strict parallel safe;

drop cast if exists ( geometry as tpv );
create cast ( geometry as tpv )
with function _tpv_from_geometry(geometry);

create or replace function _tpvarray_from_geometry(
    p_geom geometry
)
    returns tpv []
as $$
select array_agg(
    (
        ST_Force2D(z.geom),
        0,
        0,
        0,
        to_timestamp(ST_Z(z.geom)),
        null,
        null
    ) :: tpv
)
from ST_DumpPoints(
         ST_Transform(
             p_geom,
             3857
         )
     ) z
$$ language 'sql' immutable strict parallel safe;

drop cast if exists ( geometry as tpv [] );
create cast ( geometry as tpv [] )
with function _tpvarray_from_geometry(geometry);


create or replace function _tpv_from_geography(
    p_geom geography
)
    returns tpv
as $$
select (
    ST_Force2D(
        ST_Transform(
            p_geom :: geometry,
            3857
        )
    ),
    0,
    0,
    0,
    to_timestamp(0),
    null,
    null
) :: tpv
$$ language 'sql' immutable strict parallel safe;

drop cast if exists ( geography as tpv );
create cast ( geography as tpv )
with function _tpv_from_geography(geography);


create or replace function _geometry_from_tpv(
    p_tpv tpv
)
    returns geometry
as $$
select p_tpv.geom
$$ language 'sql' immutable strict parallel safe;

drop cast if exists ( tpv as geometry );
create cast ( tpv as geometry )
with function _geometry_from_tpv(tpv);


create or replace function _geography_from_tpv(
    p_tpv tpv
)
    returns geography
as $$
select ST_Transform(p_tpv.geom, 4326) :: geography
$$ language 'sql' immutable strict parallel safe;

drop cast if exists ( tpv as geography );
create cast ( tpv as geography )
with function _geography_from_tpv(tpv);


create or replace function _geometry_from_tpvarray(
    p_tpv tpv []
)
    returns geometry
as $$
select ST_MakeLine(
    geom
order by ts
)
from unnest(p_tpv) as t
$$ language 'sql' immutable strict parallel safe;

drop cast if exists ( tpv [] as geometry );
create cast ( tpv [] as geometry )
with function _geometry_from_tpvarray(tpv []);


create or replace function _geography_from_tpvarray(
    p_tpv tpv []
)
    returns geography
as $$
select ST_Transform(
    ST_MakeLine(
        geom
    order by ts
    ),
    4326
) :: geography
from unnest(p_tpv) as t
$$ language 'sql' immutable strict parallel safe;

drop cast if exists ( tpv [] as geography );
create cast ( tpv [] as geography )
with function _geography_from_tpvarray(tpv []);


create or replace function _tpvarray_from_jsonb(
    p_jsonb jsonb
)
    returns tpv []
as $$
select
    array((select
        (
            ST_Transform(
                ST_SetSRID(
                    ST_MakePoint(
                        (p_tpv ->> 'lon') :: float,
                        (p_tpv ->> 'lat') :: float
                    ),
                    4326
                ),
                3857
            ),
            p_tpv ->> 'acc',
            p_tpv ->> 'hdg',
            p_tpv ->> 'spd',
            to_timestamp((p_tpv ->> 'ts') :: float / 1000),
            p_tpv ->> 'src',
            p_tpv ->> 'osm_id'
        ) :: tpv
        from
            jsonb_array_elements(p_jsonb) as p_tpv
    )) :: tpv []
$$ language 'sql' immutable strict parallel safe;

drop cast if exists ( jsonb as tpv [] );
create cast ( jsonb as tpv [] )
with function _tpvarray_from_jsonb(jsonb);


create or replace function _jsonb_from_tpv(
    p_tpv tpv
)
    returns jsonb
as $$
select jsonb_build_object(
    'lon', ST_X(p_tpv :: geography :: geometry),
    'lat', ST_Y(p_tpv :: geography :: geometry),
    'acc', p_tpv.accuracy,
    'hdg', p_tpv.heading,
    'spd', p_tpv.speed,
    'ts', extract(epoch from p_tpv.ts) * 1000.,
    'src', p_tpv.source,
    'osm_id', p_tpv.osm_id
)
$$ language 'sql' immutable strict parallel safe;

drop cast if exists ( tpv as jsonb );
create cast ( tpv as jsonb )
with function _jsonb_from_tpv(tpv);

create or replace function ST_Distance(
    p_tpv_1 tpv,
    p_tpv_2 tpv
)
    returns float
as $$
select ST_Distance(
    p_tpv_1 :: geography,
    p_tpv_2 :: geography
)
$$ language 'sql' immutable strict parallel safe;
