summaryrefslogtreecommitdiff
path: root/lib/Travelynx/Model/InTransit.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Travelynx/Model/InTransit.pm')
-rw-r--r--lib/Travelynx/Model/InTransit.pm629
1 files changed, 590 insertions, 39 deletions
diff --git a/lib/Travelynx/Model/InTransit.pm b/lib/Travelynx/Model/InTransit.pm
index a90cd08..8324027 100644
--- a/lib/Travelynx/Model/InTransit.pm
+++ b/lib/Travelynx/Model/InTransit.pm
@@ -1,6 +1,7 @@
package Travelynx::Model::InTransit;
-# Copyright (C) 2020-2023 Birte Kristina Friesel
+# Copyright (C) 2020-2025 Birte Kristina Friesel
+# Copyright (C) 2025 networkException <git@nwex.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
@@ -9,6 +10,7 @@ use warnings;
use 5.020;
use DateTime;
+use GIS::Distance;
use JSON;
my %visibility_itoa = (
@@ -99,6 +101,8 @@ sub add {
my $train_suffix = $opt{train_suffix};
my $journey = $opt{journey};
my $stop = $opt{stop};
+ my $stopover = $opt{stopover};
+ my $manual = $opt{manual};
my $checkin_station_id = $opt{departure_eva};
my $route = $opt{route};
my $data = $opt{data};
@@ -108,8 +112,6 @@ sub add {
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
if ($train) {
-
- # IRIS
$db->insert(
'in_transit',
{
@@ -129,7 +131,7 @@ sub add {
messages => $json->encode(
[ map { [ $_->[0]->epoch, $_->[1] ] } $train->messages ]
),
- data => JSON->new->encode(
+ data => $json->encode(
{
rt => $train->departure_has_realtime ? 1
: 0,
@@ -140,9 +142,66 @@ sub add {
}
);
}
- elsif ( $journey and $stop and $journey->can('product') ) {
-
- # HAFAS
+ elsif ( $journey
+ and $stop
+ and ref($journey) eq 'Travel::Status::DE::EFA::Trip' )
+ {
+ my @route;
+ for my $j_stop ( $journey->route ) {
+ push(
+ @route,
+ [
+ $j_stop->full_name,
+ $j_stop->id_num,
+ {
+ sched_arr => _epoch( $j_stop->sched_arr ),
+ sched_dep => _epoch( $j_stop->sched_dep ),
+ rt_arr => _epoch( $j_stop->rt_arr ),
+ rt_dep => _epoch( $j_stop->rt_dep ),
+ arr_delay => $j_stop->arr_delay,
+ dep_delay => $j_stop->dep_delay,
+ platform => $j_stop->platform,
+ efa_load => $j_stop->occupancy,
+ lat => $j_stop->latlon->[0],
+ lon => $j_stop->latlon->[1],
+ }
+ ]
+ );
+ if ( $j_stop->is_cancelled ) {
+ $route[-1][2]{isCancelled} = 1;
+ }
+ }
+ $persistent_data->{operator} = $journey->operator;
+ $db->insert(
+ 'in_transit',
+ {
+ user_id => $uid,
+ cancelled => $stop->is_cancelled ? 1 : 0,
+ checkin_station_id => $stop->id_num,
+ checkin_time => $now,
+ dep_platform => $stop->platform,
+ train_type => $journey->type // q{},
+ train_line => $journey->line,
+ train_no => $journey->number // q{},
+ train_id => $opt{trip_id},
+ sched_departure => $stop->sched_dep,
+ real_departure => $stop->rt_dep // $stop->sched_dep,
+ route => $json->encode( \@route ),
+ data => $json->encode(
+ {
+ rt => $stop->rt_dep ? 1 : 0,
+ %{ $data // {} }
+ }
+ ),
+ user_data => $json->encode($persistent_data),
+ backend_id => $backend_id,
+ }
+ );
+ }
+ elsif ( $journey
+ and $stop
+ and ref($journey) eq 'Travel::Status::DE::HAFAS::Journey' )
+ {
my @route;
my $product = $journey->product_at( $stop->loc->eva )
// $journey->product;
@@ -159,6 +218,7 @@ sub add {
rt_dep => _epoch( $j_stop->rt_dep ),
arr_delay => $j_stop->arr_delay,
dep_delay => $j_stop->dep_delay,
+ platform => $j_stop->platform,
load => $j_stop->load,
lat => $j_stop->loc->lat,
lon => $j_stop->loc->lon,
@@ -169,6 +229,9 @@ sub add {
$route[-1][2]{tz_offset} = $j_stop->tz_offset;
}
}
+ if ( scalar $journey->operators ) {
+ $persistent_data->{operators} = [ $journey->operators ];
+ }
$db->insert(
'in_transit',
{
@@ -186,20 +249,26 @@ sub add {
sched_departure => $stop->{sched_dep},
real_departure => $stop->{rt_dep} // $stop->{sched_dep},
route => $json->encode( \@route ),
- data => JSON->new->encode(
+ data => $json->encode(
{
rt => $stop->{rt_dep} ? 1 : 0,
%{ $data // {} }
}
),
+ user_data => $json->encode($persistent_data),
backend_id => $backend_id,
}
);
}
- elsif ( $journey and $stop ) {
-
- # DBRIS
- my $number = $journey->train_no // $journey->number // $train_suffix;
+ elsif ( $journey
+ and $stop
+ and ref($journey) eq 'Travel::Status::DE::DBRIS::Journey' )
+ {
+ my $trip_no
+ = $journey->trip_no_at( $stop->eva,
+ $stop->sched_dep ? $stop->sched_dep->epoch : undef )
+ // $journey->train_no;
+ my $number = $trip_no // $journey->number // $train_suffix;
my $line;
if ( defined $journey->line_no and $journey->line_no ne $number ) {
@@ -217,14 +286,14 @@ sub add {
$j_stop->name,
$j_stop->eva,
{
- sched_arr => _epoch( $j_stop->sched_arr ),
- sched_dep => _epoch( $j_stop->sched_dep ),
- rt_arr => _epoch( $j_stop->rt_arr ),
- rt_dep => _epoch( $j_stop->rt_dep ),
- isCancelled => $j_stop->is_cancelled,
- arr_delay => $j_stop->arr_delay,
- dep_delay => $j_stop->dep_delay,
- load => {
+ sched_arr => _epoch( $j_stop->sched_arr ),
+ sched_dep => _epoch( $j_stop->sched_dep ),
+ rt_arr => _epoch( $j_stop->rt_arr ),
+ rt_dep => _epoch( $j_stop->rt_dep ),
+ arr_delay => $j_stop->arr_delay,
+ dep_delay => $j_stop->dep_delay,
+ platform => $j_stop->platform,
+ load => {
FIRST => $j_stop->occupancy_first,
SECOND => $j_stop->occupancy_second
},
@@ -233,6 +302,12 @@ sub add {
}
]
);
+ if ( $j_stop->is_additional ) {
+ $route[-1][2]{isAdditional} = 1;
+ }
+ if ( $j_stop->is_cancelled ) {
+ $route[-1][2]{isCancelled} = 1;
+ }
}
my @messages;
for my $msg ( $journey->messages ) {
@@ -254,6 +329,12 @@ sub add {
);
}
}
+ if ( scalar $journey->admin_ids ) {
+ $persistent_data->{admin_ids} = [ $journey->admin_ids ];
+ }
+ if ( scalar $journey->operators ) {
+ $persistent_data->{operators} = [ $journey->operators ];
+ }
$db->insert(
'in_transit',
{
@@ -271,19 +352,112 @@ sub add {
sched_departure => $stop->sched_dep,
real_departure => $stop->rt_dep // $stop->sched_dep,
route => $json->encode( \@route ),
- data => JSON->new->encode(
+ data => $json->encode(
{
rt => $stop->{rt_dep} ? 1 : 0,
%{ $data // {} }
}
),
- user_data => JSON->new->encode($persistent_data),
+ user_data => $json->encode($persistent_data),
+ backend_id => $backend_id,
+ }
+ );
+ }
+ elsif ( $journey
+ and $stopover
+ and ref($journey) eq 'Travel::Status::MOTIS::Trip' )
+ {
+ my @route;
+ for my $journey_stopover ( $journey->stopovers ) {
+ push(
+ @route,
+ [
+ $journey_stopover->stop->name,
+ $journey_stopover->stop->{eva}
+ // die('eva not set for stopover'),
+ {
+ sched_arr =>
+ _epoch( $journey_stopover->scheduled_arrival ),
+ sched_dep =>
+ _epoch( $journey_stopover->scheduled_departure ),
+ rt_arr => _epoch( $journey_stopover->realtime_arrival ),
+ rt_dep =>
+ _epoch( $journey_stopover->realtime_departure ),
+ arr_delay => $journey_stopover->arrival_delay,
+ dep_delay => $journey_stopover->departure_delay,
+ platform => $journey_stopover->track,
+ lat => $journey_stopover->stop->lat,
+ lon => $journey_stopover->stop->lon,
+ }
+ ]
+ );
+ }
+
+ $persistent_data->{operator} = $journey->agency;
+
+ $db->insert(
+ 'in_transit',
+ {
+ user_id => $uid,
+ cancelled => $stopover->{is_cancelled}
+ ? 1
+ : 0,
+ checkin_station_id => $stopover->stop->{eva},
+ checkin_time => DateTime->now( time_zone => 'Europe/Berlin' ),
+ dep_platform => $stopover->track,
+ train_type => $journey->mode,
+ train_no => q{},
+ train_id => $journey->id,
+ train_line => $journey->route_name,
+ sched_departure => $stopover->scheduled_departure,
+ real_departure => $stopover->departure,
+ route => $json->encode( \@route ),
+ data => $json->encode(
+ {
+ rt => $stopover->{is_realtime} ? 1 : 0,
+ %{ $data // {} }
+ }
+ ),
+ user_data => $json->encode($persistent_data),
+ backend_id => $backend_id,
+ }
+ );
+ }
+ elsif ($manual) {
+ if ( $manual->{comment} ) {
+ $persistent_data->{comment} = $manual->{comment};
+ }
+ $db->insert(
+ 'in_transit',
+ {
+ user_id => $uid,
+ cancelled => 0,
+ checkin_station_id => $manual->{dep_id},
+ checkout_station_id => $manual->{arr_id},
+ checkin_time => DateTime->now( time_zone => 'Europe/Berlin' ),
+ train_type => $manual->{train_type},
+ train_no => $manual->{train_no} || q{},
+ train_id => 'manual',
+ train_line => $manual->{train_line} || undef,
+ sched_departure => $manual->{sched_departure},
+ real_departure => $manual->{sched_departure},
+ sched_arrival => $manual->{sched_arrival},
+ real_arrival => $manual->{sched_arrival},
+ route => $json->encode( $manual->{route} // [] ),
+ data => $json->encode(
+ {
+ manual => \1,
+ %{ $data // {} }
+ }
+ ),
+ user_data => $json->encode($persistent_data),
backend_id => $backend_id,
}
);
+ return;
}
else {
- die('neither train nor journey specified');
+ die('invalid arguments / argument types passed to InTransit->add');
}
}
@@ -360,6 +534,14 @@ sub postprocess {
$ret->{comment} = $ret->{user_data}{comment};
$ret->{wagongroups} = $ret->{user_data}{wagongroups};
+ if ( $ret->{sched_dep_ts} and $ret->{real_dep_ts} ) {
+ $ret->{dep_delay} = $ret->{real_dep_ts} - $ret->{sched_dep_ts};
+ }
+
+ if ( $ret->{sched_arr_ts} and $ret->{real_arr_ts} ) {
+ $ret->{arr_delay} = $ret->{real_arr_ts} - $ret->{sched_arr_ts};
+ }
+
$ret->{platform_type} = 'Gleis';
if ( $ret->{train_type} and $ret->{train_type} =~ m{ ast | bus | ruf }ix ) {
$ret->{platform_type} = 'Steig';
@@ -477,6 +659,7 @@ sub get {
if ( $opt{with_polyline} and $ret ) {
$ret->{dep_latlon} = [ $ret->{dep_lat}, $ret->{dep_lon} ];
$ret->{arr_latlon} = [ $ret->{arr_lat}, $ret->{arr_lon} ];
+ $ret->{now_latlon} = $self->estimate_trip_position($ret);
}
if ( $opt{with_visibility} and $ret ) {
@@ -618,6 +801,22 @@ sub set_arrival_eva {
);
}
+sub set_arrival_platform {
+ my ( $self, %opt ) = @_;
+
+ my $uid = $opt{uid};
+ my $db = $opt{db} // $self->{pg}->db;
+ my $platform = $opt{arrival_platform};
+
+ $db->update(
+ 'in_transit',
+ {
+ arr_platform => $platform,
+ },
+ { user_id => $uid }
+ );
+}
+
sub set_arrival_times {
my ( $self, %opt ) = @_;
@@ -842,6 +1041,10 @@ sub update_departure_dbris {
my $ephemeral_data = $res_h ? $res_h->{data} : {};
my $persistent_data = $res_h ? $res_h->{user_data} : {};
+ if ( $stop->{rt_dep} ) {
+ $ephemeral_data->{rt} = 1;
+ }
+
$ephemeral_data->{him_msg} = [];
$persistent_data->{him_msg} = [];
for my $msg ( $journey->messages ) {
@@ -883,6 +1086,68 @@ sub update_departure_dbris {
);
}
+sub update_departure_efa {
+ my ( $self, %opt ) = @_;
+ my $uid = $opt{uid};
+ my $db = $opt{db} // $self->{pg}->db;
+ my $dep_eva = $opt{dep_eva};
+ my $arr_eva = $opt{arr_eva};
+ my $journey = $opt{journey};
+ my $stop = $opt{stop};
+ my $json = JSON->new;
+
+ my $res_h = $db->select( 'in_transit', ['data'], { user_id => $uid } )
+ ->expand->hash;
+ my $ephemeral_data = $res_h ? $res_h->{data} : {};
+ if ( $stop->rt_dep ) {
+ $ephemeral_data->{rt} = 1;
+ }
+
+ # selecting on user_id and train_no avoids a race condition if a user checks
+ # into a new train while we are fetching data for their previous journey. In
+ # this case, the new train would receive data from the previous journey.
+ $db->update(
+ 'in_transit',
+ {
+ data => $json->encode($ephemeral_data),
+ real_departure => $stop->rt_dep,
+ },
+ {
+ user_id => $uid,
+ train_id => $opt{trip_id},
+ checkin_station_id => $dep_eva,
+ checkout_station_id => $arr_eva,
+ }
+ );
+}
+
+sub update_departure_motis {
+ my ( $self, %opt ) = @_;
+ my $uid = $opt{uid};
+ my $db = $opt{db} // $self->{pg}->db;
+ my $dep_eva = $opt{dep_eva};
+ my $arr_eva = $opt{arr_eva};
+ my $journey = $opt{journey};
+ my $stopover = $opt{stopover};
+ my $json = JSON->new;
+
+ # selecting on user_id and train_no avoids a race condition if a user checks
+ # into a new train while we are fetching data for their previous journey. In
+ # this case, the new train would receive data from the previous journey.
+ $db->update(
+ 'in_transit',
+ {
+ real_departure => $stopover->{realtime_departure},
+ },
+ {
+ user_id => $uid,
+ train_id => $opt{train_id},
+ checkin_station_id => $dep_eva,
+ checkout_station_id => $arr_eva,
+ }
+ );
+}
+
sub update_departure_hafas {
my ( $self, %opt ) = @_;
my $uid = $opt{uid};
@@ -893,12 +1158,20 @@ sub update_departure_hafas {
my $stop = $opt{stop};
my $json = JSON->new;
+ my $res_h = $db->select( 'in_transit', ['data'], { user_id => $uid } )
+ ->expand->hash;
+ my $ephemeral_data = $res_h ? $res_h->{data} : {};
+ if ( $stop->{rt_dep} ) {
+ $ephemeral_data->{rt} = 1;
+ }
+
# selecting on user_id and train_no avoids a race condition if a user checks
# into a new train while we are fetching data for their previous journey. In
# this case, the new train would receive data from the previous journey.
$db->update(
'in_transit',
{
+ data => $json->encode($ephemeral_data),
real_departure => $stop->{rt_dep},
},
{
@@ -967,6 +1240,10 @@ sub update_arrival_dbris {
my $ephemeral_data = $res_h ? $res_h->{data} : {};
my $persistent_data = $res_h ? $res_h->{user_data} : {};
+ if ( $stop->{rt_arr} ) {
+ $ephemeral_data->{rt} = 1;
+ }
+
$ephemeral_data->{him_msg} = [];
$persistent_data->{him_msg} = [];
for my $msg ( $journey->messages ) {
@@ -997,15 +1274,15 @@ sub update_arrival_dbris {
$j_stop->name,
$j_stop->eva,
{
- sched_arr => _epoch( $j_stop->sched_arr ),
- sched_dep => _epoch( $j_stop->sched_dep ),
- rt_arr => _epoch( $j_stop->rt_arr ),
- rt_dep => _epoch( $j_stop->rt_dep ),
- platform => $j_stop->platform,
- isCancelled => $j_stop->is_cancelled,
- arr_delay => $j_stop->arr_delay,
- dep_delay => $j_stop->dep_delay,
- load => {
+ sched_arr => _epoch( $j_stop->sched_arr ),
+ sched_dep => _epoch( $j_stop->sched_dep ),
+ rt_arr => _epoch( $j_stop->rt_arr ),
+ rt_dep => _epoch( $j_stop->rt_dep ),
+ platform => $j_stop->platform,
+ arr_delay => $j_stop->arr_delay,
+ dep_delay => $j_stop->dep_delay,
+ platform => $j_stop->platform,
+ load => {
FIRST => $j_stop->occupancy_first,
SECOND => $j_stop->occupancy_second
},
@@ -1014,6 +1291,12 @@ sub update_arrival_dbris {
}
]
);
+ if ( $j_stop->is_additional ) {
+ $route[-1][2]{isAdditional} = 1;
+ }
+ if ( $j_stop->is_cancelled ) {
+ $route[-1][2]{isCancelled} = 1;
+ }
}
# selecting on user_id and train_no avoids a race condition if a user checks
@@ -1022,8 +1305,8 @@ sub update_arrival_dbris {
$db->update(
'in_transit',
{
- real_arrival => $stop->{rt_arr},
- arr_platform => $stop->{platform},
+ real_arrival => $stop->rt_arr,
+ arr_platform => $stop->platform,
route => $json->encode( [@route] ),
data => $json->encode($ephemeral_data),
user_data => $json->encode($persistent_data),
@@ -1037,6 +1320,128 @@ sub update_arrival_dbris {
);
}
+sub update_arrival_efa {
+ my ( $self, %opt ) = @_;
+ my $uid = $opt{uid};
+ my $db = $opt{db} // $self->{pg}->db;
+ my $dep_eva = $opt{dep_eva};
+ my $arr_eva = $opt{arr_eva};
+ my $journey = $opt{journey};
+ my $stop = $opt{stop};
+ my $json = JSON->new;
+
+ my $res_h
+ = $db->select( 'in_transit', [ 'data', 'route' ], { user_id => $uid } )
+ ->expand->hash;
+ my $ephemeral_data = $res_h ? $res_h->{data} : {};
+ my $old_route = $res_h ? $res_h->{route} : [];
+
+ if ( $stop->rt_arr ) {
+ $ephemeral_data->{rt} = 1;
+ }
+
+ my @route;
+ for my $j_stop ( $journey->route ) {
+ push(
+ @route,
+ [
+ $j_stop->full_name,
+ $j_stop->id_num,
+ {
+ sched_arr => _epoch( $j_stop->sched_arr ),
+ sched_dep => _epoch( $j_stop->sched_dep ),
+ rt_arr => _epoch( $j_stop->rt_arr ),
+ rt_dep => _epoch( $j_stop->rt_dep ),
+ arr_delay => $j_stop->arr_delay,
+ dep_delay => $j_stop->dep_delay,
+ platform => $j_stop->platform,
+ efa_load => $j_stop->occupancy,
+ lat => $j_stop->latlon->[0],
+ lon => $j_stop->latlon->[1],
+ }
+ ]
+ );
+ if ( $j_stop->is_cancelled ) {
+ $route[-1][2]{isCancelled} = 1;
+ }
+ }
+
+ # TODO set efa_load from old route entry if missing in current route entry
+ # (at least in VVO, occupancy data is only provided for future stops)
+
+ # selecting on user_id and train_no avoids a race condition if a user checks
+ # into a new train while we are fetching data for their previous journey. In
+ # this case, the new train would receive data from the previous journey.
+ $db->update(
+ 'in_transit',
+ {
+ data => $json->encode($ephemeral_data),
+ real_arrival => $stop->rt_arr,
+ arr_platform => $stop->platform,
+ route => $json->encode( [@route] ),
+ },
+ {
+ user_id => $uid,
+ train_id => $opt{trip_id},
+ checkin_station_id => $dep_eva,
+ checkout_station_id => $arr_eva,
+ }
+ );
+}
+
+sub update_arrival_motis {
+ my ( $self, %opt ) = @_;
+ my $uid = $opt{uid};
+ my $db = $opt{db} // $self->{pg}->db;
+ my $dep_eva = $opt{dep_eva};
+ my $arr_eva = $opt{arr_eva};
+ my $journey = $opt{journey};
+ my $stopover = $opt{stopover};
+ my $json = JSON->new;
+
+ my @route;
+ for my $journey_stopover ( $journey->stopovers ) {
+ push(
+ @route,
+ [
+ $journey_stopover->stop->name,
+ $journey_stopover->stop->{eva}
+ // die('eva not set for stopover'),
+ {
+ sched_arr => _epoch( $journey_stopover->scheduled_arrival ),
+ sched_dep =>
+ _epoch( $journey_stopover->scheduled_departure ),
+ rt_arr => _epoch( $journey_stopover->realtime_arrival ),
+ rt_dep => _epoch( $journey_stopover->realtime_departure ),
+ arr_delay => $journey_stopover->arrival_delay,
+ dep_delay => $journey_stopover->departure_delay,
+ platform => $journey_stopover->track,
+ lat => $journey_stopover->stop->lat,
+ lon => $journey_stopover->stop->lon,
+ }
+ ]
+ );
+ }
+
+ # selecting on user_id and train_no avoids a race condition if a user checks
+ # into a new train while we are fetching data for their previous journey. In
+ # this case, the new train would receive data from the previous journey.
+ $db->update(
+ 'in_transit',
+ {
+ real_arrival => $stopover->realtime_arrival,
+ arr_platform => $stopover->track,
+ route => $json->encode( [@route] ),
+ },
+ {
+ user_id => $uid,
+ train_id => $opt{train_id},
+ checkin_station_id => $dep_eva,
+ checkout_station_id => $arr_eva,
+ }
+ );
+}
+
sub update_arrival_hafas {
my ( $self, %opt ) = @_;
my $uid = $opt{uid};
@@ -1047,6 +1452,16 @@ sub update_arrival_hafas {
my $stop = $opt{stop};
my $json = JSON->new;
+ my $res_h
+ = $db->select( 'in_transit', [ 'data', 'route' ], { user_id => $uid } )
+ ->expand->hash;
+ my $ephemeral_data = $res_h ? $res_h->{data} : {};
+ my $old_route = $res_h ? $res_h->{route} : [];
+
+ if ( $stop->{rt_arr} ) {
+ $ephemeral_data->{rt} = 1;
+ }
+
my @route;
for my $j_stop ( $journey->route ) {
push(
@@ -1061,6 +1476,7 @@ sub update_arrival_hafas {
rt_dep => _epoch( $j_stop->rt_dep ),
arr_delay => $j_stop->arr_delay,
dep_delay => $j_stop->dep_delay,
+ platform => $j_stop->platform,
load => $j_stop->load,
lat => $j_stop->loc->lat,
lon => $j_stop->loc->lon,
@@ -1072,10 +1488,6 @@ sub update_arrival_hafas {
}
}
- my $res_h = $db->select( 'in_transit', ['route'], { user_id => $uid } )
- ->expand->hash;
- my $old_route = $res_h ? $res_h->{route} : [];
-
for my $i ( 0 .. $#route ) {
if ( $old_route->[$i] and $old_route->[$i][1] == $route[$i][1] ) {
for my $k (qw(rt_arr rt_dep arr_delay dep_delay)) {
@@ -1090,7 +1502,9 @@ sub update_arrival_hafas {
$db->update(
'in_transit',
{
- real_arrival => $stop->{rt_arr},
+ data => $json->encode($ephemeral_data),
+ real_arrival => $stop->rt_arr,
+ arr_platform => $stop->platform,
route => $json->encode( [@route] ),
},
{
@@ -1172,4 +1586,141 @@ sub update_visibility {
);
}
+sub estimate_trip_position_between_stops {
+ my ( $self, %opt ) = @_;
+
+ my $time_complete = $opt{now} - $opt{from_ts};
+ my $time_total = $opt{to_ts} - $opt{from_ts};
+ my $ratio = $time_complete / $time_total;
+
+ my $distance = GIS::Distance->new;
+ my $polyline = $opt{polyline};
+ my ( $i_from, $i_to );
+
+ for my $i ( 0 .. $#{$polyline} ) {
+ if ( not defined $i_from
+ and $polyline->[$i][2]
+ and $polyline->[$i][2] == $opt{from}[1] )
+ {
+ $i_from = $i;
+ }
+ elsif ( not defined $i_to
+ and $polyline->[$i][2]
+ and $polyline->[$i][2] == $opt{to}[1] )
+ {
+ $i_to = $i;
+ last;
+ }
+ }
+ if ( defined $i_from and defined $i_to ) {
+ my $total_distance = 0;
+ for my $i ( $i_from + 1 .. $i_to ) {
+ my $prev = $polyline->[ $i - 1 ];
+ my $this = $polyline->[$i];
+ if ( $prev and $this ) {
+ $total_distance
+ += $distance->distance_metal( $prev->[1], $prev->[0],
+ $this->[1], $this->[0] );
+ }
+ }
+
+ my $marker_distance = $total_distance * $ratio;
+ $total_distance = 0;
+ for my $i ( $i_from + 1 .. $i_to ) {
+ my $prev = $polyline->[ $i - 1 ];
+ my $this = $polyline->[$i];
+ if ( $prev and $this ) {
+ my $prev_distance = $total_distance;
+ $total_distance
+ += $distance->distance_metal( $prev->[1], $prev->[0],
+ $this->[1], $this->[0] );
+ if ( $total_distance > $marker_distance ) {
+ my $sub_ratio = 1;
+ if ( $total_distance != $prev_distance ) {
+ $sub_ratio = ( $marker_distance - $prev_distance )
+ / ( $total_distance - $prev_distance );
+ }
+ return (
+ $prev->[1] + ( $this->[1] - $prev->[1] ) * $sub_ratio,
+ $prev->[0] + ( $this->[0] - $prev->[0] ) * $sub_ratio,
+ );
+ }
+ }
+ }
+ }
+ return (
+ $opt{from}[2]{lat} + ( $opt{to}[2]{lat} - $opt{from}[2]{lat} ) * $ratio,
+ $opt{from}[2]{lon} + ( $opt{to}[2]{lon} - $opt{from}[2]{lon} ) * $ratio
+ );
+}
+
+sub estimate_trip_position {
+ my ( $self, $in_transit ) = @_;
+
+ my @now_latlon;
+ my @route = @{ $in_transit->{route} };
+
+ # estimate_train_position runs before postprocess, so all route
+ # timestamps are provided in UNIX seconds and not as DateTime objects.
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' )->epoch;
+
+ my $prev_ts;
+ for my $i ( 0 .. $#route ) {
+ my $ts = $route[$i][2]{rt_arr} // $route[$i][2]{sched_arr}
+ // $route[$i][2]{rt_dep} // $route[$i][2]{sched_dep} // 0;
+ my $ts_dep = $route[$i][2]{rt_dep} // $route[$i][2]{sched_dep}
+ // $route[$i][2]{rt_arr} // $route[$i][2]{sched_arr} // 0;
+ if ( $ts and $ts_dep and $now >= $ts and $now <= $ts_dep ) {
+
+ # Currently at a stop
+ @now_latlon = ( $route[$i][2]{lat}, $route[$i][2]{lon} );
+ last;
+ }
+ if ( $ts
+ and $prev_ts
+ and $now > $prev_ts
+ and $now < $ts )
+ {
+ @now_latlon = $self->estimate_trip_position_between_stops(
+ now => $now,
+ from => $route[ $i - 1 ],
+ from_ts => $prev_ts,
+ to => $route[$i],
+ to_ts => $ts,
+ polyline => $in_transit->{polyline},
+ );
+ last;
+ }
+ $prev_ts = $ts_dep;
+ }
+
+ if ( not @now_latlon
+ and $in_transit->{sched_dep_ts}
+ and $in_transit->{sched_arr_ts} )
+ {
+ my $time_complete = $now
+ - ( $in_transit->{real_dep_ts} // $in_transit->{sched_dep_ts} );
+ my $time_total
+ = ( $in_transit->{real_arr_ts} // $in_transit->{sched_arr_ts} )
+ - ( $in_transit->{real_dep_ts} // $in_transit->{sched_dep_ts} );
+
+ if ( $time_total == 0 ) {
+ return [ $in_transit->{dep_lat}, $in_transit->{dep_lon} ];
+ }
+
+ my $completion = $time_complete / $time_total;
+ $completion = $completion < 0 ? 0 : $completion > 1 ? 1 : $completion;
+ @now_latlon = (
+ $in_transit->{dep_lat}
+ + ( $in_transit->{arr_lat} - $in_transit->{dep_lat} )
+ * $completion,
+ $in_transit->{dep_lon}
+ + ( $in_transit->{arr_lon} - $in_transit->{dep_lon} )
+ * $completion,
+ );
+ }
+
+ return \@now_latlon;
+}
+
1;