/posts  ·   /talks  ·   /boat  ·   /#30DayMapChallenge 2020 | 2022  ·  /About

2022 / Day 23: Movement

Back some years ago I did a thing for estimating public transit vehicle locations in real-time using GTFS data. Including a presentation during the FOSS4G Europe 2017 conference in Paris.

Now looking back at it (being 5 years older, and hopefully wiser) the way I applied speeding up/slowing down feels a bit too complicated. Essentially the same can be achieved with sigmoid function giving the path much smoother flow aswell. There’s still something with speeds not coming down when reaching the stop but don’t have time to wrap my head around it currently. Have to leave something for next time too.

The CTE part of the query is only preparation of the data this time:

In the main query part we do union alls to fetch results for

All point locating tasks are performed with st_lineinterpolatepoint. For creating trip legs st_linesubstring does its thing.

output image

with
    trip as (
        /* TRIP GEOMETRY is a figure of 8 pushed to the side*/
        select
            st_rotate(
                st_chaikinsmoothing(
                    st_makeline(
                        array[
                            st_point(30,60,0),
                            st_point(50,100,0),
                            st_point(0,100,0),
                            st_point(50,0,0),
                            st_point(0,0,0),
                            st_point(30,60,0)
                        ]
                    ),
                    3, true
                ),
                radians(90)
            ) as geom
    ),
    timetable as (
        /* TIMETABLE is an array of seconds since start of the trip
           that the vehicle will reach a station. STATIONS themselves
           are derived by dividing the trip shape into equal length parts
           depending on how many stations are needed*/
        select
            row_number() over(order by ts) as stop_nr, ts,
            min(ts) over() as min_ts,
            max(ts) over() as max_ts,
            lead(ts) over(order by ts) as next_ts,
            lag(ts) over(order by ts) as prev_ts
        from
            unnest(
                array[
                    0,
                    75,
                    120,
                    170,
                    250,
                    300
                ]
            ) with ordinality tt(ts, i)
    ),
    legs as (
        /* a LEG starts and finishes at a station*/
        select
            ord,
            st_linesubstring(
                trip.geom,
                (ord-1)::numeric/(tt.count-1)::numeric,
                (ord)::numeric/(tt.count-1)::numeric
            ) as geom
        from
            trip
                join lateral (
                    select count(1)
                    from timetable
                ) tt on true
                join lateral
                    generate_series(
                        1, tt.count-1, 1
                    ) ord on true
    )
/* pull it all together with*/
select
    row_number() over ()::int as oid,
    ('2022.11.23 '||(((7.98 + t::numeric / 3600.0)||' hours')::interval)::time)::timestamp,
    *
from (
    /* INTERPOLATE POINT WITH SPPEDUPS AND SLOWDOWNS*/
    select
        trip.from_ts + t as t,
        loc as geom,
        case
            when
                t=0 then
                    ((fr.val * (st_length(trip.geom))) / 0.01::numeric) + 5.0
            else
                (fr.val * (st_length(trip.geom)) / (t::numeric)) + 5.0
        end as speed,
        'with_speedups' as cl
    from (
        select
            legs.ord, legs.geom,
            timetable.ts as from_ts, timetable.next_ts as to_ts
        from
            legs,
            timetable
        where
            legs.ord = timetable.stop_nr
    ) trip
        join lateral
            generate_series(0, trip.to_ts-trip.from_ts) t on true
        join lateral (
            select
                case
                    when
                        /* 5 seconds since start - still at stop*/
                        t <= 5 then 0.0
                    when
                        /* five seconds to the end - we're arrived*/
                        t >= trip.to_ts - 5 then 1.0
                    else (
                        /*  Fit by sigmoid*/
                        1.0 / (
                            1.0 + pow(exp(1),-1*(
                                (
                                    (t::numeric - 5.0) /
                                    (trip.to_ts-trip.from_ts - 5.0)::numeric
                                ) * 20.0 - 10.0
                            ))
                        )
                    )
                end as val
        ) fr on true
        join lateral
            st_lineinterpolatepoint(
                trip.geom,
                fr.val
            ) loc on true
    union all
    /* INTERPOLATE POINT BETWEEN THIS STATION AND NEXT STATION*/
    select
        trip.from_ts + t as t,
        loc as geom,
        case
            when
                t=0 or t=(trip.to_ts-trip.from_ts) then
                    ((0 * (st_length(trip.geom))) / 0.01::numeric) + 5.0
            else
                ((fr.val * (st_length(trip.geom))) / t::numeric) + 5.0
        end as speed,
        'with_stops' as cl
    from (
        select
            legs.ord, legs.geom,
            timetable.ts as from_ts, timetable.next_ts as to_ts
        from
            legs,
            timetable
        where
            legs.ord = timetable.stop_nr
    ) trip
        join lateral
            generate_series(0, trip.to_ts-trip.from_ts) t on true
        join lateral (
            select t::numeric / (trip.to_ts-trip.from_ts)::numeric  as val
        ) fr on true
        join lateral
            st_lineinterpolatepoint(
                trip.geom,
                fr.val
            ) loc on true
    union all
    /* INTERPOLATE ONLY BETWEEN FIRST AND LAST STATION*/
    select
        t,
        loc as geom,
        case
            when
                t = tt.min or t=tt.max
                    then ((0 * (st_length(trip.geom))) / 0.01::numeric) + 5.0
            else
                ((fr.val * (st_length(trip.geom))) / t::numeric)  + 5.0
        end as speed,
        'no_stops' as cl
    from
        trip
            join lateral (
                select min(ts), max(ts) from timetable
            ) tt on true
        join lateral
            generate_series(tt.min, tt.max) t on true
        join lateral (
            select t::numeric / (tt.max-tt.min)::numeric as val
        ) fr on true
        join lateral
            st_lineinterpolatepoint(
                trip.geom,
                fr.val
            ) loc on true
    union all
    /* STATIONS*/
    select
        t, st_startpoint(legs.geom), 4 as speed, 'stop' as cl
    from
        legs
            join lateral (
                select min(ts), max(ts) from timetable
            ) tt on true
            join lateral
                generate_series(tt.min, tt.max) t on true
) d
;