optionally show local transit connections as well

This commit is contained in:
Daniel Friesel 2022-09-24 18:53:04 +02:00
parent 1c7779e94e
commit e54130ad6e
No known key found for this signature in database
GPG key ID: 100D5BFB5166E005
9 changed files with 292 additions and 126 deletions

View file

@ -1083,6 +1083,29 @@ my @migrations = (
} }
); );
}, },
# v25 -> v26
# travelynx 1.24 adds local transit connections and needs to know targets
# for that to work, as local transit does not support checkins yet.
sub {
my ($db) = @_;
$db->query(
qq{
create table localtransit (
user_id integer not null references users (id) primary key,
data jsonb
);
create view user_transit as select
id,
use_history,
localtransit.data as data
from users
left join localtransit on localtransit.user_id = id
;
update schema_version set version = 26;
}
);
},
); );
sub setup_db { sub setup_db {

View file

@ -149,6 +149,7 @@ sub run {
my $transit_res = $db->delete( 'in_transit', { user_id => $uid } ); my $transit_res = $db->delete( 'in_transit', { user_id => $uid } );
my $hooks_res = $db->delete( 'webhooks', { user_id => $uid } ); my $hooks_res = $db->delete( 'webhooks', { user_id => $uid } );
my $trwl_res = $db->delete( 'traewelling', { user_id => $uid } ); my $trwl_res = $db->delete( 'traewelling', { user_id => $uid } );
my $lt_res = $db->delete( 'localtransit', { user_id => $uid } );
my $password_res my $password_res
= $db->delete( 'pending_passwords', { user_id => $uid } ); = $db->delete( 'pending_passwords', { user_id => $uid } );
my $user_res = $db->delete( 'users', { id => $uid } ); my $user_res = $db->delete( 'users', { id => $uid } );

View file

@ -499,7 +499,10 @@ sub insight {
my ($self) = @_; my ($self) = @_;
my $user = $self->current_user; my $user = $self->current_user;
my $use_history = $self->users->use_history( uid => $user->{id} ); my ( $use_history, $destinations ) = $self->users->use_history(
uid => $user->{id},
with_local_transit => 1
);
if ( $self->param('action') and $self->param('action') eq 'save' ) { if ( $self->param('action') and $self->param('action') eq 'save' ) {
if ( $self->param('on_departure') ) { if ( $self->param('on_departure') ) {
@ -516,9 +519,22 @@ sub insight {
$use_history &= ~0x02; $use_history &= ~0x02;
} }
if ( $self->param('local_transit') ) {
$use_history |= 0x04;
}
else {
$use_history &= ~0x04;
}
if ( $self->param('destinations') ) {
$destinations
= [ split( qr{\r?\n\r?}, $self->param('destinations') ) ];
}
$self->users->use_history( $self->users->use_history(
uid => $user->{id}, uid => $user->{id},
set => $use_history set => $use_history,
destinations => $destinations
); );
$self->flash( success => 'use_history' ); $self->flash( success => 'use_history' );
$self->redirect_to('account'); $self->redirect_to('account');
@ -526,6 +542,8 @@ sub insight {
$self->param( on_departure => $use_history & 0x01 ? 1 : 0 ); $self->param( on_departure => $use_history & 0x01 ? 1 : 0 );
$self->param( on_arrival => $use_history & 0x02 ? 1 : 0 ); $self->param( on_arrival => $use_history & 0x02 ? 1 : 0 );
$self->param( local_transit => $use_history & 0x04 ? 1 : 0 );
$self->param( destinations => join( "\n", @{$destinations} ) );
$self->render('use_history'); $self->render('use_history');
} }

View file

@ -29,7 +29,10 @@ sub get_connecting_trains_p {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
my $uid = $opt{uid} //= $self->current_user->{id}; my $uid = $opt{uid} //= $self->current_user->{id};
my $use_history = $self->users->use_history( uid => $uid ); my ( $use_history, $lt_stops ) = $self->users->use_history(
uid => $uid,
with_local_transit => 1
);
my ( $eva, $exclude_via, $exclude_train_id, $exclude_before ); my ( $eva, $exclude_via, $exclude_train_id, $exclude_before );
my $now = $self->now->epoch; my $now = $self->now->epoch;
@ -72,7 +75,7 @@ sub get_connecting_trains_p {
@destinations = grep { $_ ne $exclude_via } @destinations; @destinations = grep { $_ ne $exclude_via } @destinations;
} }
if ( not @destinations ) { if ( not( @destinations or $use_history & 0x04 and @{$lt_stops} ) ) {
return $promise->reject; return $promise->reject;
} }
@ -82,6 +85,7 @@ sub get_connecting_trains_p {
my $iris_promise = Mojo::Promise->new; my $iris_promise = Mojo::Promise->new;
if (@destinations) {
$self->iris->get_departures_p( $self->iris->get_departures_p(
station => $eva, station => $eva,
lookbehind => 10, lookbehind => 10,
@ -137,8 +141,9 @@ sub get_connecting_trains_p {
# properly by the cancellation logic etc. # properly by the cancellation logic etc.
if ( $train->departure_is_cancelled ) { if ( $train->departure_is_cancelled ) {
my @via my @via = (
= ( $train->sched_route_post, $train->sched_route_end ); $train->sched_route_post, $train->sched_route_end
);
for my $dest (@destinations) { for my $dest (@destinations) {
if ( has_str_in_list( $dest, @via ) ) { if ( has_str_in_list( $dest, @via ) ) {
push( @cancellations, [ $train, $dest ] ); push( @cancellations, [ $train, $dest ] );
@ -177,11 +182,13 @@ sub get_connecting_trains_p {
} @results; } @results;
@cancellations = map { $_->[0] } @cancellations = map { $_->[0] }
sort { $a->[1] <=> $b->[1] } sort { $a->[1] <=> $b->[1] }
map { [ $_, $_->[0]->sched_departure->epoch ] } @cancellations; map { [ $_, $_->[0]->sched_departure->epoch ] }
@cancellations;
# remove trains whose route matches the excluded one's # remove trains whose route matches the excluded one's
if ($excluded_train) { if ($excluded_train) {
my $route_pre = join( '|', reverse $excluded_train->route_pre ); my $route_pre
= join( '|', reverse $excluded_train->route_pre );
@results @results
= grep { join( '|', $_->[0]->route_post ) ne $route_pre } = grep { join( '|', $_->[0]->route_post ) ne $route_pre }
@results; @results;
@ -200,7 +207,8 @@ sub get_connecting_trains_p {
my $interchange_duration; my $interchange_duration;
if ( exists $stationinfo->{i} ) { if ( exists $stationinfo->{i} ) {
$interchange_duration $interchange_duration
= $stationinfo->{i}{$arr_platform}{ $train->platform }; = $stationinfo->{i}{$arr_platform}
{ $train->platform };
$interchange_duration //= $stationinfo->{i}{"*"}; $interchange_duration //= $stationinfo->{i}{"*"};
} }
if ( defined $interchange_duration ) { if ( defined $interchange_duration ) {
@ -227,6 +235,10 @@ sub get_connecting_trains_p {
return; return;
} }
)->wait; )->wait;
}
else {
$iris_promise->resolve( [] );
}
my $hafas_promise = Mojo::Promise->new; my $hafas_promise = Mojo::Promise->new;
my $rest_api = $self->config->{backend}{hafas_rest_api}; my $rest_api = $self->config->{backend}{hafas_rest_api};
@ -254,6 +266,7 @@ sub get_connecting_trains_p {
my ( $iris, $hafas ) = @_; my ( $iris, $hafas ) = @_;
my @iris_trains = @{ $iris->[0] }; my @iris_trains = @{ $iris->[0] };
my @hafas_trains = @{ $hafas->[0] }; my @hafas_trains = @{ $hafas->[0] };
my @transit_fyi;
my $strp = DateTime::Format::Strptime->new( my $strp = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%dT%H:%M:%S%z', pattern => '%Y-%m-%dT%H:%M:%S%z',
@ -296,6 +309,44 @@ sub get_connecting_trains_p {
} }
} }
} }
if ( $use_history & 0x04 and @{$lt_stops} ) {
my %via_count = map { $_ => 0 } @{$lt_stops};
for my $hafas_train (@hafas_trains) {
for
my $stop ( @{ $hafas_train->{nextStopovers} // [] } )
{
for my $dest ( @{$lt_stops} ) {
if ( $stop->{stop}{name}
and $stop->{stop}{name} eq $dest
and $via_count{$dest} < 2
and $hafas_train->{when} )
{
my $departure = $strp->parse_datetime(
$hafas_train->{when} );
my $arrival
= $strp->parse_datetime(
$stop->{arrival} );
if ( $departure->epoch >= $exclude_before )
{
$via_count{$dest}++;
push(
@transit_fyi,
[
{
line =>
$hafas_train->{line}
{name},
departure => $departure,
},
$dest, $arrival
]
);
}
}
}
}
}
}
}; };
if ($@) { if ($@) {
$self->app->log->error( $self->app->log->error(
@ -303,7 +354,7 @@ sub get_connecting_trains_p {
); );
} }
$promise->resolve( \@iris_trains ); $promise->resolve( \@iris_trains, \@transit_fyi );
return; return;
} }
)->catch( )->catch(

View file

@ -9,6 +9,7 @@ use warnings;
use 5.020; use 5.020;
use DateTime; use DateTime;
use JSON;
my @sb_templates = ( my @sb_templates = (
undef, undef,
@ -483,14 +484,36 @@ sub use_history {
my $uid = $opt{uid}; my $uid = $opt{uid};
my $value = $opt{set}; my $value = $opt{set};
if ( $opt{destinations} ) {
$db->insert(
'localtransit',
{
user_id => $uid,
data =>
JSON->new->encode( { destinations => $opt{destinations} } )
},
{ on_conflict => \'(user_id) do update set data = EXCLUDED.data' }
);
}
if ($value) { if ($value) {
$db->update( 'users', { use_history => $value }, { id => $uid } ); $db->update( 'users', { use_history => $value }, { id => $uid } );
} }
else {
if ( $opt{with_local_transit} ) {
my $res = $db->select(
'user_transit',
[ 'use_history', 'data' ],
{ id => $uid }
)->expand->hash;
return ( $res->{use_history}, $res->{data}{destinations} // [] );
}
else { else {
return $db->select( 'users', ['use_history'], { id => $uid } ) return $db->select( 'users', ['use_history'], { id => $uid } )
->hash->{use_history}; ->hash->{use_history};
} }
} }
}
sub use_external_services { sub use_external_services {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;

View file

@ -200,6 +200,13 @@
% } % }
%= include '_connections', connections => \@connections, checkin_from => $journey->{arrival_countdown} < 0 ? $journey->{arr_ds100} : undef; %= include '_connections', connections => \@connections, checkin_from => $journey->{arrival_countdown} < 0 ? $journey->{arr_ds100} : undef;
% } % }
% if (my @transit_fyi = @{stash('transit_fyi') // []}) {
<span class="card-title" style="margin-top: 2ex;">Nahverkehr</span>
% if ($journey->{arrival_countdown} < 0) {
<p>Nur zur Information kein Checkin möglich.</p>
% }
%= include '_transit_fyi', transit_fyi => \@transit_fyi;
% }
% if (defined $journey->{arrival_countdown} and $journey->{arrival_countdown} <= 0) { % if (defined $journey->{arrival_countdown} and $journey->{arrival_countdown} <= 0) {
<p style="margin-top: 2ex;"> <p style="margin-top: 2ex;">
Der automatische Checkout erfolgt wegen gelegentlich veralteter Der automatische Checkout erfolgt wegen gelegentlich veralteter

View file

@ -0,0 +1,19 @@
<table class="striped"><tbody>
% for my $res (@{$transit_fyi}) {
% my ($info, $via, $via_arr) = @{$res};
% $via_arr = $via_arr ? $via_arr->strftime('%H:%M') : q{};
<tr>
<td>
%= $info->{line}
<br/>
%= $info->{departure}->strftime('%H:%M')
% if ($info->{departure_delay}) {
%= sprintf('(%+d)', $info->{departure_delay})
% }
</td>
<td>
<%= $via %><br/><%= $via_arr %>
</td>
</tr>
% }
</tbody></table>

View file

@ -61,7 +61,7 @@
<th scope="row">Verbindungen</th> <th scope="row">Verbindungen</th>
<td> <td>
<a href="/account/insight"><i class="material-icons">edit</i></a> <a href="/account/insight"><i class="material-icons">edit</i></a>
% if ($use_history & 0x03) { % if ($use_history & 0x07) {
Vorschläge aktiv Vorschläge aktiv
% } % }
% else { % else {

View file

@ -47,6 +47,30 @@
ohne Umweg über die Abfahrtstafel möglich. ohne Umweg über die Abfahrtstafel möglich.
</div> </div>
</div> </div>
<div class="row">
<div class="input-field col s12">
<label>
%= check_box local_transit => 1
<span>Nahverkehr</span>
</label>
</div>
</div>
<div class="row">
<div class="col s12">
Zeige beim Reisestatus zusätzlich Anschlussmöglichkeiten an den
Nahverkehr. Diese dienen lediglich zur Information; ein Checkin ist
nicht möglich. Es werden nur Anschlussmöglichkeiten zu Zielen
angezeigt, die im folgenden Feld gelistet sind (ein Ziel pro
Zeile, z.B. „Eichlinghofen H-Bahn, Dortmund“). Falls travelynx in
Zukunft eine Möglichkeit für Checkins in Nahverkehrsmittel erhält,
wird diese Liste ggf. gelöscht.
</div>
</div>
<div class="row">
<div class="col s12">
%= text_area 'destinations', id => 'destinations', class => 'materialize-textarea'
</div>
</div>
<div class="row"> <div class="row">
<div class="col s3 m3 l3"> <div class="col s3 m3 l3">
</div> </div>