summaryrefslogtreecommitdiff
path: root/lib/DBInfoscreen/Controller/Stationboard.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/DBInfoscreen/Controller/Stationboard.pm')
-rw-r--r--lib/DBInfoscreen/Controller/Stationboard.pm1463
1 files changed, 1015 insertions, 448 deletions
diff --git a/lib/DBInfoscreen/Controller/Stationboard.pm b/lib/DBInfoscreen/Controller/Stationboard.pm
index 24df23e..84fac61 100644
--- a/lib/DBInfoscreen/Controller/Stationboard.pm
+++ b/lib/DBInfoscreen/Controller/Stationboard.pm
@@ -1,6 +1,6 @@
package DBInfoscreen::Controller::Stationboard;
-# Copyright (C) 2011-2020 Daniel Friesel
+# Copyright (C) 2011-2020 Birte Kristina Friesel
#
# SPDX-License-Identifier: AGPL-3.0-or-later
@@ -8,31 +8,101 @@ use Mojo::Base 'Mojolicious::Controller';
use DateTime;
use DateTime::Format::Strptime;
-use Encode qw(decode encode);
-use File::Slurp qw(read_file write_file);
-use List::Util qw(max uniq);
+use Encode qw(decode encode);
+use File::Slurp qw(read_file write_file);
+use List::Util qw(max uniq);
+use List::UtilsBy qw(uniq_by);
use List::MoreUtils qw();
-use Mojo::JSON qw(decode_json);
+use Mojo::JSON qw(decode_json encode_json);
use Mojo::Promise;
use Mojo::UserAgent;
+use Travel::Status::DE::DBWagenreihung;
+use Travel::Status::DE::HAFAS;
use Travel::Status::DE::IRIS;
use Travel::Status::DE::IRIS::Stations;
use XML::LibXML;
use utf8;
-no if $] >= 5.018, warnings => 'experimental::smartmatch';
-
my %default = (
mode => 'app',
admode => 'deparr',
);
+sub class_to_product {
+ my ( $self, $hafas ) = @_;
+
+ my $bits = $hafas->get_active_service->{productbits};
+ my $ret;
+
+ for my $i ( 0 .. $#{$bits} ) {
+ $ret->{ 2**$i }
+ = ref( $bits->[$i] ) eq 'ARRAY' ? $bits->[$i][0] : $bits->[$i];
+ }
+
+ return $ret;
+}
+
sub handle_no_results {
- my ( $self, $station, $data ) = @_;
+ my ( $self, $station, $data, $hafas ) = @_;
my $errstr = $data->{errstr};
+ if ($hafas) {
+ $self->render_later;
+ my $service = 'DB';
+ if ( $hafas ne '1' and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $service = $hafas;
+ }
+ Travel::Status::DE::HAFAS->new_p(
+ locationSearch => $station,
+ service => $service,
+ promise => 'Mojo::Promise',
+ user_agent => $self->ua,
+ )->then(
+ sub {
+ my ($status) = @_;
+ my @candidates = $status->results;
+ @candidates = map { [ $_->name, $_->eva ] } @candidates;
+ if ( @candidates == 1 and $candidates[0][0] ne $station ) {
+ my $s = $candidates[0][0];
+ my $params = $self->req->params->to_string;
+ $self->redirect_to("/${s}?${params}");
+ return;
+ }
+ for my $candidate (@candidates) {
+ $candidate->[0] =~ s{[&]#x0028;}{(}g;
+ $candidate->[0] =~ s{[&]#x0029;}{)}g;
+ }
+ my $err;
+ if ( not $errstr =~ m{LOCATION} ) {
+ $err = $errstr;
+ }
+ $self->render(
+ 'landingpage',
+ error => $err,
+ stationlist => \@candidates,
+ hide_opts => 0,
+ status => $data->{status} // 300,
+ );
+ return;
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->render(
+ 'landingpage',
+ error => ( $err // "Keine Abfahrten an '$station'" ),
+ hide_opts => 0,
+ status => $data->{status} // 500,
+ );
+ return;
+ }
+ )->wait;
+ return;
+ }
+
my @candidates = map { [ $_->[1], $_->[0] ] }
Travel::Status::DE::IRIS::Stations::get_station($station);
if (
@@ -46,7 +116,7 @@ sub handle_no_results {
'landingpage',
stationlist => \@candidates,
hide_opts => 0,
- status => 300,
+ status => $data->{status} // 300,
);
return;
}
@@ -56,14 +126,16 @@ sub handle_no_results {
'landingpage',
error => ( $errstr // "Keine Abfahrten an '$station'" )
. '. Das von DBF genutzte IRIS-Backend unterstützt im Regelfall nur innerdeutsche Zugfahrten.',
- hide_opts => 0
+ hide_opts => 0,
+ status => $data->{status} // 200,
);
return;
}
$self->render(
'landingpage',
error => ( $errstr // "Keine Abfahrten an '$station'" ),
- hide_opts => 0
+ hide_opts => 0,
+ status => $data->{status} // 404,
);
return;
}
@@ -79,7 +151,6 @@ sub handle_no_results_json {
if ($errstr) {
$json = {
api_version => $api_version,
- version => $self->config->{version},
error => $errstr,
};
}
@@ -91,7 +162,6 @@ sub handle_no_results_json {
{
$json = {
api_version => $api_version,
- version => $self->config->{version},
error => 'ambiguous station code/name',
candidates => \@candidates,
};
@@ -99,7 +169,6 @@ sub handle_no_results_json {
else {
$json = {
api_version => $api_version,
- version => $self->config->{version},
error => ( $errstr // "Got no results for '$station'" )
};
}
@@ -108,12 +177,13 @@ sub handle_no_results_json {
$json = $self->render_to_string( json => $json );
$self->render(
data => "$callback($json);",
- format => 'json'
+ format => 'json',
);
}
else {
$self->render(
- json => $json,
+ json => $json,
+ status => $data->{status} // 300,
);
}
return;
@@ -161,7 +231,9 @@ sub result_has_train_type {
sub result_has_via {
my ( $result, $via ) = @_;
- my @route = $result->route_post;
+ my @route
+ = $result->can('route_post') ? $result->route_post : map { $_->loc->name }
+ $result->route;
my $eq_result = List::MoreUtils::any { lc eq lc($via) } @route;
@@ -186,11 +258,15 @@ sub result_has_via {
}
sub log_api_access {
+ my ($suffix) = @_;
+ $suffix //= q{};
+
+ my $file = "$ENV{DBFAKEDISPLAY_STATS}${suffix}";
my $counter = 1;
- if ( -r $ENV{DBFAKEDISPLAY_STATS} ) {
- $counter = read_file( $ENV{DBFAKEDISPLAY_STATS} ) + 1;
+ if ( -r $file ) {
+ $counter = read_file($file) + 1;
}
- write_file( $ENV{DBFAKEDISPLAY_STATS}, $counter );
+ write_file( $file, $counter );
return;
}
@@ -211,7 +287,13 @@ sub json_route_diff {
}
# this branch is inefficient, but won't be taken frequently
- elsif ( not( $route[$route_idx] ~~ \@sched_route ) ) {
+ elsif (
+ not(
+ List::MoreUtils::any { $route[$route_idx] eq $_ }
+ @sched_route
+ )
+ )
+ {
push(
@json_route,
{
@@ -258,13 +340,29 @@ sub json_route_diff {
}
sub get_results_p {
- my ( $station, %opt ) = @_;
+ my ( $self, $station, %opt ) = @_;
my $data;
- # Cache::File has UTF-8 problems, so strip it (and any other potentially
- # problematic chars).
- my $cache_str = $station;
- $cache_str =~ tr{[0-9a-zA-Z -]}{}cd;
+ if ( $opt{hafas} ) {
+ my $service = 'DB';
+ if ( $opt{hafas} ne '1'
+ and Travel::Status::DE::HAFAS::get_service( $opt{hafas} ) )
+ {
+ $service = $opt{hafas};
+ }
+ return Travel::Status::DE::HAFAS->new_p(
+ service => $service,
+ station => $station,
+ arrivals => $opt{arrivals},
+ cache => $opt{cache_iris_rt},
+ lwp_options => {
+ timeout => 10,
+ agent => 'dbf.finalrewind.org/2'
+ },
+ promise => 'Mojo::Promise',
+ user_agent => $self->ua,
+ );
+ }
if ( $ENV{DBFAKEDISPLAY_STATS} ) {
log_api_access();
@@ -275,6 +373,12 @@ sub get_results_p {
# if we have an exact match. Ask the backend otherwise.
my @station_matches
= Travel::Status::DE::IRIS::Stations::get_station($station);
+
+ # Requests with EVA codes can be handled even if we do not know about them.
+ if ( @station_matches != 1 and $station =~ m{^\d+$} ) {
+ @station_matches = ( [ undef, undef, $station ] );
+ }
+
if ( @station_matches == 1 ) {
$station = $station_matches[0][2];
return Travel::Status::DE::IRIS->new_p(
@@ -308,11 +412,13 @@ sub handle_request {
my $station = $self->stash('station');
my $template = $self->param('mode') // 'app';
+ my $hafas = $self->param('hafas');
my $with_related = !$self->param('no_related');
my %opt = (
cache_iris_main => $self->app->cache_iris_main,
cache_iris_rt => $self->app->cache_iris_rt,
- lookahead => $self->config->{lookahead}
+ lookahead => $self->config->{lookahead},
+ hafas => $hafas,
);
if ( $self->param('past') ) {
@@ -321,13 +427,22 @@ sub handle_request {
$opt{lookahead} += 60;
}
+ if ( $self->param('admode') and $self->param('admode') eq 'arr' ) {
+ $opt{arrivals} = 1;
+ }
+
my $api_version = $Travel::Status::DE::IRIS::VERSION;
$self->stash( departures => [] );
$self->stash( title => 'DBF' );
- $self->stash( version => $self->config->{version} );
- if ( not( $template ~~ [qw[app infoscreen json multi single text]] ) ) {
+ if (
+ not(
+ List::MoreUtils::any { $template eq $_ }
+ (qw(app infoscreen json multi single text))
+ )
+ )
+ {
$template = 'app';
}
@@ -354,7 +469,12 @@ sub handle_request {
# (or used by) marudor.de, it was renamed to 'json'. Many clients won't
# notice this for year to come, so we make sure mode=marudor still works as
# intended.
- if ( $template eq 'marudor' ) {
+ if (
+ $template eq 'marudor'
+ or ( $self->req->headers->accept
+ and $self->req->headers->accept eq 'application/json' )
+ )
+ {
$template = 'json';
}
@@ -375,8 +495,8 @@ sub handle_request {
if ( $self->param('train') and not $opt{datetime} ) {
- # request results from twenty minutes ago to avoid train details suddenly
- # becoming unavailable when its scheduled departure is reached.
+ # request results from twenty minutes ago to avoid train details suddenly
+ # becoming unavailable when its scheduled departure is reached.
$opt{datetime} = DateTime->now( time_zone => 'Europe/Berlin' )
->subtract( minutes => 20 );
$opt{lookahead} = $self->config->{lookahead} + 20;
@@ -384,13 +504,21 @@ sub handle_request {
$self->render_later;
- get_results_p( $station, %opt )->then(
+ $self->get_results_p( $station, %opt )->then(
sub {
my ($status) = @_;
my $data = {
results => [ $status->results ],
+ hafas => $hafas ? $status : undef,
station_ds100 =>
( $status->station ? $status->station->{ds100} : undef ),
+ station_eva => (
+ $status->station
+ ? ( $status->station->{uic} // $status->station->{eva} )
+ : undef
+ ),
+ station_evas =>
+ ( $status->station ? $status->station->{evas} : [] ),
station_name =>
( $status->station ? $status->station->{name} : $station ),
};
@@ -400,7 +528,7 @@ sub handle_request {
return;
}
if ( not @{ $data->{results} } ) {
- $self->handle_no_results( $station, $data );
+ $self->handle_no_results( $station, $data, $hafas );
return;
}
$self->handle_result($data);
@@ -409,11 +537,24 @@ sub handle_request {
sub {
my ($err) = @_;
if ( $template eq 'json' ) {
- $self->handle_no_results_json( $station, { errstr => $err },
- $api_version );
+ $self->handle_no_results_json(
+ $station,
+ {
+ errstr => $err,
+ status => ( $err =~ m{Ambiguous|LOCATION} ? 300 : 500 ),
+ },
+ $api_version
+ );
return;
}
- $self->handle_no_results( $station, { errstr => $err } );
+ $self->handle_no_results(
+ $station,
+ {
+ errstr => $err,
+ status => ( $err =~ m{Ambiguous|LOCATION} ? 300 : 500 ),
+ },
+ $hafas
+ );
return;
}
)->wait;
@@ -507,7 +648,7 @@ sub format_iris_result_info {
if ( $template ne 'json' ) {
push(
@{$moreinfo},
- [ 'Außerplanmäßiger Halt in', $additional_line ]
+ [ 'Außerplanmäßiger Halt in', { text => $additional_line } ]
);
}
}
@@ -517,7 +658,7 @@ sub format_iris_result_info {
$info
= 'Ohne Halt in: ' . $cancel_line . ( $info ? ' +++ ' : q{} ) . $info;
if ( $template ne 'json' ) {
- push( @{$moreinfo}, [ 'Ohne Halt in', $cancel_line ] );
+ push( @{$moreinfo}, [ 'Ohne Halt in', { text => $cancel_line } ] );
}
}
@@ -529,19 +670,21 @@ sub format_iris_result_info {
sub render_train {
my ( $self, $result, $departure, $station_name, $template ) = @_;
- $departure->{links} = [];
- $departure->{route_pre_diff} = [
- $self->json_route_diff(
- [ $result->route_pre ],
- [ $result->sched_route_pre ]
- )
- ];
- $departure->{route_post_diff} = [
- $self->json_route_diff(
- [ $result->route_post ],
- [ $result->sched_route_post ]
- )
- ];
+ $departure->{links} = [];
+ if ( $result->can('route_pre') ) {
+ $departure->{route_pre_diff} = [
+ $self->json_route_diff(
+ [ $result->route_pre ],
+ [ $result->sched_route_pre ]
+ )
+ ];
+ $departure->{route_post_diff} = [
+ $self->json_route_diff(
+ [ $result->route_post ],
+ [ $result->sched_route_post ]
+ )
+ ];
+ }
if ( not $result->has_realtime ) {
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
@@ -554,35 +697,62 @@ sub render_train {
}
my $linetype = 'bahn';
- my @classes = $result->classes;
- if ( @classes == 0 ) {
- $linetype = 'ext';
- }
- elsif ( grep { $_ eq 'S' } @classes ) {
- $linetype = 'sbahn';
+
+ if ( $result->can('classes') ) {
+ my @classes = $result->classes;
+ if ( @classes == 0 ) {
+ $linetype = 'ext';
+ }
+ elsif ( grep { $_ eq 'S' } @classes ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( grep { $_ eq 'F' } @classes ) {
+ $linetype = 'fern';
+ }
}
- elsif ( grep { $_ eq 'F' } @classes ) {
- $linetype = 'fern';
+ elsif ( $result->can('class') ) {
+ if ( $result->class <= 2 ) {
+ $linetype = 'fern';
+ }
+ elsif ( $result->class == 16 ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( $result->class == 32 ) {
+ $linetype = 'bus';
+ }
+ elsif ( $result->class == 128 ) {
+ $linetype = 'ubahn';
+ }
+ elsif ( $result->class == 256 ) {
+ $linetype = 'tram';
+ }
}
$self->render_later;
my $wagonorder_req = Mojo::Promise->new;
- my $utilization_req = Mojo::Promise->new;
my $occupancy_req = Mojo::Promise->new;
my $stationinfo_req = Mojo::Promise->new;
my $route_req = Mojo::Promise->new;
- my @requests = (
- $wagonorder_req, $utilization_req, $occupancy_req,
- $stationinfo_req, $route_req
- );
+ my @requests
+ = ( $wagonorder_req, $occupancy_req, $stationinfo_req, $route_req );
if ( $departure->{wr_link} ) {
- $self->wagonorder->is_available_p( $result, $departure->{wr_link} )
+ $self->wagonorder->get_p( $result->train_no, $departure->{wr_link} )
->then(
sub {
- # great!
+ my ($wr_json) = @_;
+ eval {
+ my $wr
+ = Travel::Status::DE::DBWagenreihung->new(
+ from_json => $wr_json );
+ $departure->{wr} = $wr;
+ $departure->{wr_text} = join( q{ • },
+ map { $_->{short} }
+ grep { $_->{short} } $wr->train_descriptions );
+ };
+ $departure->{wr_text} ||= 'Wagen';
return;
},
sub {
@@ -595,33 +765,12 @@ sub render_train {
return;
}
)->wait;
-
- # Looks like utilization data is only available for long-distance trains
- # – and the few regional trains which also have wagon order data (e.g.
- # around Stuttgart). Funky.
- $self->marudor->get_train_utilization( train => $result )->then(
- sub {
- my ( $first, $second ) = @_;
- $departure->{utilization} = [ $first, $second ];
- return;
- },
- sub {
- $departure->{utilization} = undef;
- return;
- }
- )->finally(
- sub {
- $utilization_req->resolve;
- return;
- }
- )->wait;
}
else {
$wagonorder_req->resolve;
- $utilization_req->resolve;
}
- $self->marudor->get_efa_occupancy(
+ $self->efa->get_efa_occupancy(
eva => $result->station_uic,
train_no => $result->train_no
)->then(
@@ -696,112 +845,88 @@ sub render_train {
}
)->wait;
- $self->hafas->get_route_timestamps_p( train => $result )->then(
- sub {
- my ( $route_ts, $route_info, $trainsearch ) = @_;
+ my %opt = ( train => $result );
- $departure->{trip_id} = $trainsearch->{trip_id};
+ #if ( $self->languages =~ m{^en} ) {
+ # $opt{language} = 'en';
+ #}
- # If a train number changes on the way, IRIS routes are incomplete,
- # whereas HAFAS data has all stops -> merge HAFAS stops into IRIS
- # stops. This is a rare case, one point where it can be observed is
- # the TGV service at Frankfurt/Karlsruhe/Mannheim.
- if ( $route_info
- and my @hafas_stations = @{ $route_info->{stations} // [] } )
- {
- if ( my @iris_stations = @{ $departure->{route_pre_diff} } ) {
- my @missing_pre;
- for my $station (@hafas_stations) {
- if (
- List::MoreUtils::any { $_->{name} eq $station }
- @iris_stations
- )
- {
- unshift(
- @{ $departure->{route_pre_diff} },
- @missing_pre
- );
- last;
+ $self->hafas->get_route_p(%opt)->then(
+ sub {
+ my ( $route, $journey ) = @_;
+
+ $departure->{trip_id} = $journey->id;
+ $departure->{operators} = [ $journey->operators ];
+ $departure->{date} = $route->[0]{sched_dep} // $route->[0]{dep};
+
+ # Use HAFAS route as source of truth; ignore IRIS data
+ $departure->{route_pre_diff} = [];
+ $departure->{route_post_diff} = $route;
+ my $split;
+ for my $i ( 0 .. $#{ $departure->{route_post_diff} } ) {
+ if ( $departure->{route_post_diff}[$i]{name} eq $station_name )
+ {
+ $split = $i;
+ if ( my $load = $route->[$i]{load} ) {
+ if ( %{$load} ) {
+ $departure->{utilization}
+ = [ $load->{FIRST}, $load->{SECOND} ];
}
- push(
- @missing_pre,
- {
- name => $station,
- hafas => 1
- }
- );
}
+ $departure->{tz_offset} = $route->[$i]{tz_offset};
+ $departure->{local_dt_da} = $route->[$i]{local_dt_da};
+ $departure->{local_sched_arr}
+ = $route->[$i]{local_sched_arr};
+ $departure->{local_sched_dep}
+ = $route->[$i]{local_sched_dep};
+ $departure->{is_annotated} = $route->[$i]{is_annotated};
+ $departure->{prod_name} = $route->[$i]{prod_name};
+ $departure->{direction} = $route->[$i]{direction};
+ $departure->{operator} = $route->[$i]{operator};
+ last;
}
- if ( my @iris_stations = @{ $departure->{route_post_diff} } ) {
- my @missing_post;
- for my $station ( reverse @hafas_stations ) {
- if (
- List::MoreUtils::any { $_->{name} eq $station }
- @iris_stations
- )
- {
- push(
- @{ $departure->{route_post_diff} },
- @missing_post
- );
- last;
- }
- unshift(
- @missing_post,
- {
- name => $station,
- hafas => 1
- }
- );
- }
+ }
+
+ if ( defined $split ) {
+ for my $i ( 0 .. $split - 1 ) {
+ push(
+ @{ $departure->{route_pre_diff} },
+ shift( @{ $departure->{route_post_diff} } )
+ );
}
+
+ # remove entry for $station_name
+ shift( @{ $departure->{route_post_diff} } );
}
- if ($route_ts) {
- if ( $route_ts->{ $result->station }{rt_bogus} ) {
- #$departure->{missing_realtime} = 1;
+ my @him_messages;
+ my @him_details;
+ for my $message ( $journey->messages ) {
+ if ( $message->code ) {
+ push( @him_details,
+ [ $message->short // q{}, { text => $message->text } ]
+ );
}
- for my $elem (
- @{ $departure->{route_pre_diff} },
- @{ $departure->{route_post_diff} }
- )
- {
- for my $key ( keys %{ $route_ts->{ $elem->{name} } // {} } )
- {
- $elem->{$key} = $route_ts->{ $elem->{name} }{$key};
- }
- if ( $elem->{rt_bogus} ) {
- $departure->{partially_missing_realtime} = 1;
- }
+ else {
+ push( @him_messages,
+ [ $message->short // q{}, { text => $message->text } ]
+ );
}
}
- if ( $route_info and @{ $route_info->{messages} // [] } ) {
- my $him = $route_info->{messages};
- my @him_messages;
- $departure->{messages}{him} = $him;
- for my $message ( @{$him} ) {
- if ( $message->{display} ) {
- push( @him_messages,
- [ $message->{header}, $message->{lead} ] );
- if ( $message->{lead} =~ m{zuginfo.nrw/?\?msg=(\d+)} ) {
- push(
- @{ $departure->{links} },
- [
- "Großstörung",
- "https://zuginfo.nrw/?msg=$1"
- ]
- );
- }
- }
+ for my $m (@him_messages) {
+ if ( $m->[0] =~ s{: Information.}{:} ) {
+ $m->[1]{icon} = 'info_outline';
+ }
+ elsif ( $m->[0] =~ s{: Störung.}{: } ) {
+ $m->[1]{icon} = 'warning';
}
- for my $message ( @{ $departure->{moreinfo} // [] } ) {
- my $m = $message->[1];
- @him_messages
- = grep { $_->[0] !~ m{Information\. $m\.$} }
- @him_messages;
+ elsif ( $m->[0] =~ s{: Bauarbeiten.}{: } ) {
+ $m->[1]{icon} = 'build';
}
- unshift( @{ $departure->{moreinfo} }, @him_messages );
+ $m->[0] =~ s{(?!<)->}{ → };
}
+ unshift( @{ $departure->{moreinfo} }, @him_messages );
+ unshift( @{ $departure->{details} }, @him_details );
}
)->catch(
sub {
@@ -814,59 +939,45 @@ sub render_train {
}
)->wait;
- # currently useless due to lack of Open Data
- if ( 0 and $self->param('detailed') ) {
- my $cycle_req = Mojo::Promise->new;
- push( @requests, $cycle_req );
- $self->wagonorder->has_cycle_p( $result->train_no )->then(
- sub {
- $departure->{has_cycle} = 1;
- }
- )->catch(
- sub {
- # nop
- }
- )->finally(
- sub {
- $cycle_req->resolve;
- return;
- }
- )->wait;
- $departure->{composition}
- = $self->app->train_details_db->{ $departure->{train_no} };
- my @cycle_from;
- my @cycle_to;
- for my $cycle ( values %{ $departure->{composition}->{cycle} // {} } ) {
- push( @cycle_from, @{ $cycle->{from} // [] } );
- push( @cycle_to, @{ $cycle->{to} // [] } );
- }
- @cycle_from = sort { $a <=> $b } uniq @cycle_from;
- @cycle_to = sort { $a <=> $b } uniq @cycle_to;
- $departure->{cycle_from}
- = [ map { [ $_, $self->app->train_details_db->{$_} ] } @cycle_from ];
- $departure->{cycle_to}
- = [ map { [ $_, $self->app->train_details_db->{$_} ] } @cycle_to ];
- }
-
# Defer rendering until all requests have completed
Mojo::Promise->all(@requests)->then(
sub {
- $self->render(
- $template // '_train_details',
- departure => $departure,
- linetype => $linetype,
- icetype => $self->app->ice_type_map->{ $departure->{train_no} },
- details => $departure->{composition} // {},
- dt_now => DateTime->now( time_zone => 'Europe/Berlin' ),
- station_name => $station_name,
- nav_link =>
- $self->url_for( 'station', station => $station_name )
- ->query( { detailed => $self->param('detailed') } ),
+ $self->respond_to(
+ json => {
+ json => {
+ departure => $departure,
+ station_name => $station_name,
+ },
+ },
+ any => {
+ template => $template // '_train_details',
+ description => sprintf(
+ '%s %s%s%s nach %s',
+ $departure->{train_type},
+ $departure->{train_line} // $departure->{train_no},
+ $departure->{origin} ? ' von ' : q{},
+ $departure->{origin} // q{},
+ $departure->{destination} // 'unbekannt'
+ ),
+ departure => $departure,
+ linetype => $linetype,
+ dt_now => DateTime->now( time_zone => 'Europe/Berlin' ),
+ station_name => $station_name,
+ nav_link =>
+ $self->url_for( 'station', station => $station_name )
+ ->query(
+ {
+ detailed => $self->param('detailed'),
+ hafas => $self->param('hafas')
+ }
+ ),
+ },
);
}
)->wait;
}
+# /z/:train/*station
sub station_train_details {
my ($self) = @_;
my $train_no = $self->stash('train');
@@ -876,6 +987,10 @@ sub station_train_details {
delete $self->stash->{layout};
}
+ if ( $station =~ s{ [.] json $ }{}x ) {
+ $self->stash( format => 'json' );
+ }
+
my %opt = (
cache_iris_main => $self->app->cache_iris_main,
cache_iris_rt => $self->app->cache_iris_rt,
@@ -898,9 +1013,20 @@ sub station_train_details {
$opt{lookahead} = $self->config->{lookahead} + 20;
}
+ # Berlin Hbf exists twice:
+ # - BLS / 8011160
+ # - BL / 8098160 (formerly "Berlin Hbf (tief)")
+ # Right now DBF assumes that station name -> EVA / DS100 is a unique map.
+ # This is not the case. Work around it here until dbf has been adjusted
+ # properly.
+ if ( $station eq 'Berlin Hbf' ) {
+ $opt{with_related} = 1;
+ }
+
$self->render_later;
- get_results_p( $station, %opt )->then(
+ # Always performs an IRIS request
+ $self->get_results_p( $station, %opt )->then(
sub {
my ($status) = @_;
my ($result)
@@ -926,6 +1052,8 @@ sub station_train_details {
departure => $result->departure
? $result->departure->strftime('%H:%M')
: undef,
+ arrival_hidden => $result->arrival_hidden,
+ departure_hidden => $result->departure_hidden,
train_type => $result->type // '',
train_line => $result->line_no,
train_no => $result->train_no,
@@ -938,6 +1066,8 @@ sub station_train_details {
arrival_is_cancelled => $result->arrival_is_cancelled,
moreinfo => $moreinfo,
delay => $result->delay,
+ arrival_delay => $result->arrival_delay,
+ departure_delay => $result->departure_delay,
route_pre => [ $result->route_pre ],
route_post => [ $result->route_post ],
replaced_by => [
@@ -950,6 +1080,8 @@ sub station_train_details {
wr_link => $result->sched_departure
? $result->sched_departure->strftime('%Y%m%d%H%M')
: undef,
+ eva => $result->station_uic,
+ start => $result->start,
};
$self->stash( title => $status->station->{name}
@@ -966,22 +1098,31 @@ sub station_train_details {
)->catch(
sub {
my ($errstr) = @_;
- $self->render(
- 'landingpage',
- error =>
- "Keine Abfahrt von $train_no in $station gefunden: $errstr",
- status => 404,
+ $self->respond_to(
+ json => {
+ json => {
+ error =>
+"Keine Abfahrt von $train_no in $station gefunden: $errstr",
+ },
+ status => 404,
+ },
+ any => {
+ template => 'landingpage',
+ error =>
+"Keine Abfahrt von $train_no in $station gefunden: $errstr",
+ status => 404,
+ },
);
return;
}
)->wait;
}
+# /z/:train
sub train_details {
my ($self) = @_;
- my $train = $self->stash('train');
-
- my ( $train_type, $train_no ) = ( $train =~ m{ ^ (\S+) \s+ (.*) $ }x );
+ my $train = $self->stash('train');
+ my $hafas = $self->param('hafas');
# TODO error handling
@@ -993,12 +1134,11 @@ sub train_details {
$self->stash( departures => [] );
$self->stash( title => 'DBF' );
- $self->stash( version => $self->config->{version} );
my $res = {
- train_type => $train_type,
+ train_type => undef,
train_line => undef,
- train_no => $train_no,
+ train_no => undef,
route_pre_diff => [],
route_post_diff => [],
moreinfo => [],
@@ -1006,92 +1146,236 @@ sub train_details {
replacement_for => [],
};
- $self->stash( title => "${train_type} ${train_no}" );
- $self->stash( hide_opts => 1 );
+ my %opt;
+
+ if ( $train =~ m{[|]} ) {
+ $opt{trip_id} = $train;
+ }
+ else {
+ my ( $train_type, $train_no ) = ( $train =~ m{ ^ (\S+) \s+ (.*) $ }x );
+ $res->{train_type} = $train_type;
+ $res->{train_no} = $train_no;
+ $self->stash( title => "${train_type} ${train_no}" );
+ $opt{train_type} = $train_type;
+ $opt{train_no} = $train_no;
+ }
+
+ my $service = 'DB';
+ if ( $hafas
+ and $hafas ne '1'
+ and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $opt{service} = $hafas;
+ }
+
+ #if ( $self->languages =~ m{^en} ) {
+ # $opt{language} = 'en';
+ #}
+ if ( my $date = $self->param('date') ) {
+ if ( $date
+ =~ m{ ^ (?<day> \d{1,2} ) [.] (?<month> \d{1,2} ) [.] (?<year> \d{4})? $ }x
+ )
+ {
+ $opt{datetime} = DateTime->now( time_zone => 'Europe/Berlin' );
+ $opt{datetime}->set(
+ day => $+{day},
+ month => $+{month}
+ );
+ if ( $+{year} ) {
+ $opt{datetime}->set( year => $+{year} );
+ }
+ }
+ }
+
+ $self->stash( hide_opts => 1 );
$self->render_later;
my $linetype = 'bahn';
- $self->hafas->get_route_timestamps_p(
- train_req => "${train_type} $train_no" )->then(
+ $self->hafas->get_route_p(%opt)->then(
sub {
- my ( $route_ts, $route_info, $trainsearch ) = @_;
+ my ( $route, $journey, $hafas_obj ) = @_;
- $res->{trip_id} = $trainsearch->{trip_id};
+ $res->{trip_id} = $journey->id;
+ $res->{date} = $route->[0]{sched_dep} // $route->[0]{dep};
- if ( not defined $trainsearch->{trainClass} ) {
- $linetype = 'ext';
- }
- elsif ( $trainsearch->{trainClass} <= 2 ) {
- $linetype = 'fern';
+ my $product = $journey->product;
+
+ if ( my $req_name = $self->param('highlight') ) {
+ if ( my $p = $journey->product_at($req_name) ) {
+ $product = $p;
+ }
}
- elsif ( $trainsearch->{trainClass} <= 8 ) {
- $linetype = 'bahn';
+
+ my $train_type = $res->{train_type} = $product->type // q{};
+ my $train_no = $res->{train_no} = $product->number // q{};
+ $res->{train_line} = $product->line_no // q{};
+ $self->stash( title => $train_type . ' '
+ . ( $train_no || $res->{train_line} ) );
+
+ if ( not defined $product->class ) {
+ $linetype = 'ext';
}
- elsif ( $trainsearch->{trainClass} <= 16 ) {
- $linetype = 'sbahn';
+ else {
+ my $prod
+ = $self->class_to_product($hafas_obj)->{ $product->class }
+ // q{};
+ if ( $prod eq 'ice' or $prod eq 'ic_ec' ) {
+ $linetype = 'fern';
+ }
+ elsif ( $prod eq 's' ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( $prod eq 'bus' ) {
+ $linetype = 'bus';
+ }
+ elsif ( $prod eq 'u' ) {
+ $linetype = 'ubahn';
+ }
+ elsif ( $prod eq 'tram' ) {
+ $linetype = 'tram';
+ }
}
- $res->{origin} = $route_info->{stations}[0];
- $res->{destination} = $route_info->{stations}[-1];
+ $res->{origin} = $journey->route_start;
+ $res->{destination} = $journey->route_end;
+ $res->{operators} = [ $journey->operators ];
- $res->{route_post_diff}
- = [ map { { name => $_ } } @{ $route_info->{stations} } ];
+ $res->{route_post_diff} = $route;
- if ($route_ts) {
- for my $elem ( @{ $res->{route_post_diff} } ) {
- for my $key ( keys %{ $route_ts->{ $elem->{name} } // {} } )
- {
- $elem->{$key} = $route_ts->{ $elem->{name} }{$key};
+ if ( my $req_name = $self->param('highlight') ) {
+ my $split;
+ for my $i ( 0 .. $#{ $res->{route_post_diff} } ) {
+ if ( $res->{route_post_diff}[$i]{name} eq $req_name ) {
+ $split = $i;
+ last;
+ }
+ }
+ if ( defined $split ) {
+ $self->stash( station_name => $req_name );
+ for my $i ( 0 .. $split - 1 ) {
+ push(
+ @{ $res->{route_pre_diff} },
+ shift( @{ $res->{route_post_diff} } )
+ );
+ }
+ my $station_info = shift( @{ $res->{route_post_diff} } );
+ $res->{eva} = $station_info->{eva};
+ if ( $station_info->{sched_arr} ) {
+ $res->{sched_arrival}
+ = $station_info->{sched_arr}->strftime('%H:%M');
+ }
+ if ( $station_info->{rt_arr} ) {
+ $res->{arrival}
+ = $station_info->{rt_arr}->strftime('%H:%M');
+ }
+ if ( $station_info->{sched_dep} ) {
+ $res->{sched_departure}
+ = $station_info->{sched_dep}->strftime('%H:%M');
+ }
+ if ( $station_info->{rt_dep} ) {
+ $res->{departure}
+ = $station_info->{rt_dep}->strftime('%H:%M');
}
+ $res->{arrival_is_cancelled}
+ = $station_info->{arr_cancelled};
+ $res->{departure_is_cancelled}
+ = $station_info->{dep_cancelled};
+ $res->{is_cancelled} = $res->{arrival_is_cancelled}
+ || $res->{arrival_is_cancelled};
+ $res->{tz_offset} = $station_info->{tz_offset};
+ $res->{local_dt_da} = $station_info->{local_dt_da};
+ $res->{local_sched_arr} = $station_info->{local_sched_arr};
+ $res->{local_sched_dep} = $station_info->{local_sched_dep};
+ $res->{is_annotated} = $station_info->{is_annotated};
+ $res->{prod_name} = $station_info->{prod_name};
+ $res->{direction} = $station_info->{direction};
+ $res->{operator} = $station_info->{operator};
+ $res->{platform} = $station_info->{platform};
+ $res->{scheduled_platform}
+ = $station_info->{sched_platform};
}
}
- if ( $route_info and @{ $route_info->{messages} // [] } ) {
- my $him = $route_info->{messages};
- my @him_messages;
- for my $message ( @{$him} ) {
- if ( $message->{display} ) {
- push( @him_messages,
- [ $message->{header}, $message->{lead} ] );
- if ( $message->{lead} =~ m{zuginfo.nrw/?\?msg=(\d+)} ) {
- push(
- @{ $res->{links} },
- [
- "Großstörung",
- "https://zuginfo.nrw/?msg=$1"
- ]
- );
- }
- }
+ my @him_messages;
+ my @him_details;
+ for my $message ( $journey->messages ) {
+ if ( $message->code ) {
+ push( @him_details,
+ [ $message->short // q{}, { text => $message->text } ]
+ );
+ }
+ else {
+ push( @him_messages,
+ [ $message->short // q{}, { text => $message->text } ]
+ );
+ }
+ }
+ for my $m (@him_messages) {
+ if ( $m->[0] =~ s{: Information.}{:} ) {
+ $m->[1]{icon} = 'info_outline';
+ }
+ elsif ( $m->[0] =~ s{: Störung.}{: } ) {
+ $m->[1]{icon} = 'warning';
}
+ elsif ( $m->[0] =~ s{: Bauarbeiten.}{: } ) {
+ $m->[1]{icon} = 'build';
+ }
+ }
+ if (@him_messages) {
$res->{moreinfo} = [@him_messages];
}
+ if (@him_details) {
+ $res->{details} = [@him_details];
+ }
- $self->render(
- $self->param('ajax') ? '_train_details' : 'train_details',
- departure => $res,
- linetype => $linetype,
- icetype => $self->app->ice_type_map->{ $res->{train_no} },
- details => {}, #$departure->{composition} // {},
- dt_now => DateTime->now( time_zone => 'Europe/Berlin' ),
-
- #station_name => "FIXME",#$station_name,
+ $self->respond_to(
+ json => {
+ json => {
+ journey => $journey,
+ },
+ },
+ any => {
+ template => $self->param('ajax')
+ ? '_train_details'
+ : 'train_details',
+ description => sprintf(
+ '%s %s%s%s nach %s',
+ $res->{train_type},
+ $res->{train_line} // $res->{train_no},
+ $res->{origin} ? ' von ' : q{},
+ $res->{origin} // q{},
+ $res->{destination} // 'unbekannt'
+ ),
+ departure => $res,
+ linetype => $linetype,
+ dt_now => DateTime->now( time_zone => 'Europe/Berlin' ),
+ },
);
}
)->catch(
sub {
my ($e) = @_;
if ($e) {
- $self->render(
- 'exception',
- exception => $e,
- snapshot => {}
+ $self->respond_to(
+ json => {
+ json => {
+ error => $e,
+ },
+ status => 500,
+ },
+ any => {
+ template => 'exception',
+ message => $e,
+ exception => undef,
+ snapshot => {},
+ status => 500,
+ },
);
}
else {
- $self->render('not_found');
+ $self->render( 'not_found', status => 404 );
}
}
)->wait;
@@ -1114,6 +1398,8 @@ sub handle_result {
my $apiver = $self->param('version') // 0;
my $callback = $self->param('callback');
my $via = $self->param('via');
+ my $hafas = $self->param('hafas');
+ my $hafas_obj = $data->{hafas};
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
@@ -1124,13 +1410,12 @@ sub handle_result {
if ( $template eq 'single' ) {
if ( not @platforms ) {
for my $result (@results) {
+ my $num_part
+ = $self->numeric_platform_part( $result->platform );
if (
- not( $self->numeric_platform_part( $result->platform ) ~~
- \@platforms )
- )
+ not( List::MoreUtils::any { $num_part eq $_ } @platforms ) )
{
- push( @platforms,
- $self->numeric_platform_part( $result->platform ) );
+ push( @platforms, $num_part );
}
}
@platforms = sort { $a <=> $b } @platforms;
@@ -1145,7 +1430,10 @@ sub handle_result {
}
if ($show_realtime) {
- if ( $admode eq 'arr' ) {
+ if ($hafas) {
+ @results = sort { $a->datetime <=> $b->datetime } @results;
+ }
+ elsif ( $admode eq 'arr' ) {
@results = sort {
( $a->arrival // $a->departure )
<=> ( $b->arrival // $b->departure )
@@ -1159,44 +1447,77 @@ sub handle_result {
}
}
+ my $class_to_product
+ = $hafas_obj ? $self->class_to_product($hafas_obj) : {};
+
@results = $self->filter_results(@results);
for my $result (@results) {
my $platform = ( split( qr{ }, $result->platform // '' ) )[0];
my $delay = $result->delay;
- if ( $admode eq 'arr' and not $result->arrival ) {
+ if ( $admode eq 'arr' and not $hafas and not $result->arrival ) {
next;
}
- if ( $admode eq 'dep'
+ if ( $admode eq 'dep'
+ and not $hafas
and not $result->departure )
{
next;
}
- my ( $info, $moreinfo )
- = $self->format_iris_result_info( $template, $result );
+ my ( $info, $moreinfo );
+ if ( $result->can('replacement_for') ) {
+ ( $info, $moreinfo )
+ = $self->format_iris_result_info( $template, $result );
+ }
- my $time = $result->time;
+ my $time
+ = $result->can('time')
+ ? $result->time
+ : $result->sched_datetime->strftime('%H:%M');
my $linetype = 'bahn';
- my @classes = $result->classes;
- if ( @classes == 0 ) {
- $linetype = 'ext';
- }
- elsif ( grep { $_ eq 'S' } @classes ) {
- $linetype = 'sbahn';
+ if ( $result->can('classes') ) {
+ my @classes = $result->classes;
+ if ( @classes == 0 ) {
+ $linetype = 'ext';
+ }
+ elsif ( grep { $_ eq 'S' } @classes ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( grep { $_ eq 'F' } @classes ) {
+ $linetype = 'fern';
+ }
}
- elsif ( grep { $_ eq 'F' } @classes ) {
- $linetype = 'fern';
+ elsif ( $result->can('class') ) {
+ my $prod = $class_to_product->{ $result->class } // q{};
+ if ( $prod eq 'ice' or $prod eq 'ic_ec' ) {
+ $linetype = 'fern';
+ }
+ elsif ( $prod eq 's' ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( $prod eq 'bus' ) {
+ $linetype = 'bus';
+ }
+ elsif ( $prod eq 'u' ) {
+ $linetype = 'ubahn';
+ }
+ elsif ( $prod eq 'tram' ) {
+ $linetype = 'tram';
+ }
}
# ->time defaults to dep, so we only need to overwrite $time
# if we want arrival times
- if ( $admode eq 'arr' ) {
+ if ( $admode eq 'arr' and not $hafas ) {
$time = $result->sched_arrival->strftime('%H:%M');
}
if ($show_realtime) {
- if ( ( $admode eq 'arr' and $result->arrival )
+ if ($hafas) {
+ $time = $result->datetime->strftime('%H:%M');
+ }
+ elsif ( ( $admode eq 'arr' and $result->arrival )
or not $result->departure )
{
$time = $result->arrival->strftime('%H:%M');
@@ -1216,8 +1537,14 @@ sub handle_result {
}
if ( $template eq 'json' ) {
- my @json_route = $self->json_route_diff( [ $result->route ],
- [ $result->sched_route ] );
+ my @json_route;
+ if ( $result->can('sched_route') ) {
+ @json_route = $self->json_route_diff( [ $result->route ],
+ [ $result->sched_route ] );
+ }
+ else {
+ @json_route = map { $_->TO_JSON } $result->route;
+ }
if ( $apiver eq '1' or $apiver eq '2' ) {
@@ -1233,29 +1560,136 @@ sub handle_result {
return;
}
else { # apiver == 3
- my ( $delay_arr, $delay_dep, $sched_arr, $sched_dep );
- if ( $result->arrival ) {
- $delay_arr = $result->arrival->subtract_datetime(
- $result->sched_arrival )->in_units('minutes');
- }
- if ( $result->departure ) {
- $delay_dep = $result->departure->subtract_datetime(
- $result->sched_departure )->in_units('minutes');
- }
- if ( $result->sched_arrival ) {
- $sched_arr = $result->sched_arrival->strftime('%H:%M');
+ if ( $result->isa('Travel::Status::DE::IRIS::Result') ) {
+ my ( $delay_arr, $delay_dep, $sched_arr, $sched_dep );
+ if ( $result->arrival ) {
+ $delay_arr = $result->arrival->subtract_datetime(
+ $result->sched_arrival )->in_units('minutes');
+ }
+ if ( $result->departure ) {
+ $delay_dep = $result->departure->subtract_datetime(
+ $result->sched_departure )->in_units('minutes');
+ }
+ if ( $result->sched_arrival ) {
+ $sched_arr = $result->sched_arrival->strftime('%H:%M');
+ }
+ if ( $result->sched_departure ) {
+ $sched_dep
+ = $result->sched_departure->strftime('%H:%M');
+ }
+ push(
+ @departures,
+ {
+ delayArrival => $delay_arr,
+ delayDeparture => $delay_dep,
+ destination => $result->destination,
+ isCancelled => $result->is_cancelled,
+ messages => {
+ delay => [
+ map {
+ {
+ timestamp => $_->[0],
+ text => $_->[1]
+ }
+ } $result->delay_messages
+ ],
+ qos => [
+ map {
+ {
+ timestamp => $_->[0],
+ text => $_->[1]
+ }
+ } $result->qos_messages
+ ],
+ },
+ missingRealtime => (
+ (
+ not $result->has_realtime
+ and $result->start < $now
+ ) ? \1 : \0
+ ),
+ platform => $result->platform,
+ route => \@json_route,
+ scheduledPlatform => $result->sched_platform,
+ scheduledArrival => $sched_arr,
+ scheduledDeparture => $sched_dep,
+ train => $result->train,
+ trainClasses => [ $result->classes ],
+ trainNumber => $result->train_no,
+ via => [ $result->route_interesting(3) ],
+ }
+ );
}
- if ( $result->sched_departure ) {
- $sched_dep = $result->sched_departure->strftime('%H:%M');
+ else {
+ push(
+ @departures,
+ {
+ delay => $result->delay,
+ direction => $result->direction,
+ destination => $result->destination,
+ isCancelled => $result->is_cancelled,
+ messages => [ $result->messages ],
+ platform => $result->platform,
+ route => \@json_route,
+ scheduledPlatform => $result->sched_platform,
+ scheduledTime => $result->sched_datetime->epoch,
+ time => $result->datetime->epoch,
+ train => $result->line,
+ trainNumber => $result->number,
+ via => [ $result->route_interesting(3) ],
+ }
+ );
}
+ }
+ }
+ elsif ( $template eq 'text' ) {
+ push(
+ @departures,
+ [
+ sprintf( '%5s %s%s',
+ $result->is_cancelled ? '--:--' : $time,
+ ( $delay and $delay > 0 ) ? q{+} : q{},
+ $delay || q{} ),
+ $result->train,
+ $result->destination,
+ $platform // q{ }
+ ]
+ );
+ }
+ else {
+ if ( $result->can('replacement_for') ) {
push(
@departures,
{
- delayArrival => $delay_arr,
- delayDeparture => $delay_dep,
- destination => $result->destination,
- isCancelled => $result->is_cancelled,
- messages => {
+ time => $time,
+ sched_arrival => $result->sched_arrival
+ ? $result->sched_arrival->strftime('%H:%M')
+ : undef,
+ sched_departure => $result->sched_departure
+ ? $result->sched_departure->strftime('%H:%M')
+ : undef,
+ arrival => $result->arrival
+ ? $result->arrival->strftime('%H:%M')
+ : undef,
+ departure => $result->departure
+ ? $result->departure->strftime('%H:%M')
+ : undef,
+ train => $result->train,
+ train_type => $result->type // '',
+ train_line => $result->line_no,
+ train_no => $result->train_no,
+ via => [ $result->route_interesting(3) ],
+ destination => $result->destination,
+ origin => $result->origin,
+ platform => $result->platform,
+ scheduled_platform => $result->sched_platform,
+ info => $info,
+ is_cancelled => $result->is_cancelled,
+ departure_is_cancelled =>
+ $result->departure_is_cancelled,
+ arrival_is_cancelled => $result->arrival_is_cancelled,
+ linetype => $linetype,
+ messages => {
delay => [
map {
{
@@ -1273,104 +1707,82 @@ sub handle_result {
} $result->qos_messages
],
},
- missingRealtime => (
- (
- not $result->has_realtime
- and $result->start < $now
- ) ? \1 : \0
+ station => $result->station,
+ moreinfo => $moreinfo,
+ delay => $delay,
+ arrival_delay => $result->arrival_delay,
+ departure_delay => $result->departure_delay,
+ missing_realtime => (
+ not $result->has_realtime
+ and $result->start < $now ? 1 : 0
),
- platform => $result->platform,
- route => \@json_route,
- scheduledPlatform => $result->sched_platform,
- scheduledArrival => $sched_arr,
- scheduledDeparture => $sched_dep,
- train => $result->train,
- trainClasses => [ $result->classes ],
- trainNumber => $result->train_no,
- via => [ $result->route_interesting(3) ],
+ route_pre => [ $result->route_pre ],
+ route_post => [ $result->route_post ],
+ additional_stops => [ $result->additional_stops ],
+ canceled_stops => [ $result->canceled_stops ],
+ replaced_by => [
+ map { $_->type . q{ } . $_->train_no }
+ $result->replaced_by
+ ],
+ replacement_for => [
+ map { $_->type . q{ } . $_->train_no }
+ $result->replacement_for
+ ],
+ wr_link => $result->sched_departure
+ ? $result->sched_departure->strftime('%Y%m%d%H%M')
+ : undef,
}
);
}
- }
- elsif ( $template eq 'text' ) {
- push(
- @departures,
- [
- sprintf( '%5s %s%s',
- $result->is_cancelled ? '--:--' : $time,
- ( $delay and $delay > 0 ) ? q{+} : q{},
- $delay || q{} ),
- $result->train,
- $result->destination,
- $platform // q{ }
- ]
- );
- }
- else {
- push(
- @departures,
- {
- time => $time,
- sched_arrival => $result->sched_arrival
- ? $result->sched_arrival->strftime('%H:%M')
- : undef,
- sched_departure => $result->sched_departure
- ? $result->sched_departure->strftime('%H:%M')
- : undef,
- arrival => $result->arrival
- ? $result->arrival->strftime('%H:%M')
- : undef,
- departure => $result->departure
- ? $result->departure->strftime('%H:%M')
- : undef,
- train => $result->train,
- train_type => $result->type // '',
- train_line => $result->line_no,
- train_no => $result->train_no,
- via => [ $result->route_interesting(3) ],
- destination => $result->destination,
- origin => $result->origin,
- platform => $result->platform,
- scheduled_platform => $result->sched_platform,
- info => $info,
- is_cancelled => $result->is_cancelled,
- departure_is_cancelled => $result->departure_is_cancelled,
- arrival_is_cancelled => $result->arrival_is_cancelled,
- linetype => $linetype,
- messages => {
- delay => [
- map { { timestamp => $_->[0], text => $_->[1] } }
- $result->delay_messages
- ],
- qos => [
- map { { timestamp => $_->[0], text => $_->[1] } }
- $result->qos_messages
- ],
- },
- station => $result->station,
- moreinfo => $moreinfo,
- delay => $delay,
- missing_realtime => (
- not $result->has_realtime
- and $result->start < $now ? 1 : 0
- ),
- route_pre => [ $result->route_pre ],
- route_post => [ $result->route_post ],
- additional_stops => [ $result->additional_stops ],
- canceled_stops => [ $result->canceled_stops ],
- replaced_by => [
- map { $_->type . q{ } . $_->train_no }
- $result->replaced_by
- ],
- replacement_for => [
- map { $_->type . q{ } . $_->train_no }
- $result->replacement_for
- ],
- wr_link => $result->sched_departure
- ? $result->sched_departure->strftime('%Y%m%d%H%M')
- : undef,
+ else {
+ my $city = q{};
+ if ( $result->station =~ m{ , ([^,]+) $ }x ) {
+ $city = $1;
}
- );
+ push(
+ @departures,
+ {
+ time => $time,
+ sched_departure =>
+ ( $result->sched_datetime and $admode ne 'arr' )
+ ? $result->sched_datetime->strftime('%H:%M')
+ : undef,
+ departure =>
+ ( $result->rt_datetime and $admode ne 'arr' )
+ ? $result->rt_datetime->strftime('%H:%M')
+ : undef,
+ train => $result->name,
+ train_type => q{},
+ train_line => $result->line,
+ train_no => $result->number,
+ journey_id => $result->id,
+ via => [
+ map { $_->loc->name =~ s{,\Q$city\E}{}r }
+ $result->route_interesting(3)
+ ],
+ destination => $result->route_end =~ s{,\Q$city\E}{}r,
+ origin => $result->route_end =~ s{,\Q$city\E}{}r,
+ platform => $result->platform,
+ scheduled_platform => $result->sched_platform,
+ info => $info,
+ is_cancelled => $result->is_cancelled,
+ linetype => $linetype,
+ station => $result->station,
+ moreinfo => $moreinfo,
+ delay => $delay,
+ replaced_by => [],
+ replacement_for => [],
+ route_pre => $admode eq 'arr'
+ ? [ map { $_->loc->name } $result->route ]
+ : [],
+ route_post => $admode eq 'arr' ? []
+ : [ map { $_->loc->name } $result->route ],
+ wr_link => $result->sched_datetime
+ ? $result->sched_datetime->strftime('%Y%m%d%H%M')
+ : undef,
+ }
+ );
+ }
if ( $self->param('train') ) {
$self->render_train( $result, $departures[-1],
$data->{station_name} // $self->stash('station') );
@@ -1416,10 +1828,36 @@ sub handle_result {
}
else {
my $station_name = $data->{station_name} // $self->stash('station');
+ my ( $api_link, $api_text, $api_icon );
+ my $params = $self->req->params->clone;
+ $params->param( hafas => not $params->param('hafas') );
+ if ( $params->param('hafas') ) {
+ if ( $data->{station_eva} >= 8100000
+ and $data->{station_eva} < 8200000 )
+ {
+ $params->param( hafas => 'ÖBB' );
+ }
+ $api_link = '/' . $data->{station_eva} . '?' . $params->to_string;
+ $api_text = 'Auf Nahverkehr wechseln';
+ $api_icon = 'train';
+ }
+ else {
+ my $iris_eva = List::Util::min grep { $_ >= 1000000 }
+ @{ $data->{station_evas} // [] };
+ if ($iris_eva) {
+ $api_link = '/' . $iris_eva . '?' . $params->to_string;
+ $api_text = 'Auf Bahnverkehr wechseln';
+ $api_icon = 'directions';
+ }
+ }
$self->render(
$template,
+ description => 'Abfahrtstafel '
+ . ( $via ? "$station_name via $via" : $station_name ),
+ api_link => $api_link,
+ api_text => $api_text,
+ api_icon => $api_icon,
departures => \@departures,
- ice_type => $self->app->ice_type_map,
station => $station_name,
version => $self->config->{version},
title => $via ? "$station_name → $via" : $station_name,
@@ -1432,8 +1870,13 @@ sub handle_result {
or $template eq 'multi'
),
force_mobile => ( $template eq 'app' ),
- nav_link => $self->url_for( 'station', station => $station_name )
- ->query( { detailed => $self->param('detailed') } ),
+ nav_link =>
+ $self->url_for( 'station', station => $station_name )->query(
+ {
+ detailed => $self->param('detailed'),
+ hafas => $self->param('hafas')
+ }
+ ),
);
}
return;
@@ -1442,29 +1885,153 @@ sub handle_result {
sub stations_by_coordinates {
my $self = shift;
- my $lon = $self->param('lon');
- my $lat = $self->param('lat');
+ my $lon = $self->param('lon');
+ my $lat = $self->param('lat');
+ my $hafas = $self->param('hafas');
if ( not $lon or not $lat ) {
$self->render( json => { error => 'Invalid lon/lat received' } );
+ return;
}
- else {
- my @candidates = map {
- {
- ds100 => $_->[0][0],
- name => $_->[0][1],
- eva => $_->[0][2],
- lon => $_->[0][3],
- lat => $_->[0][4],
- distance => $_->[1],
- }
- } Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon,
- $lat, 10 );
- $self->render(
- json => {
- candidates => [@candidates],
+
+ my $service = 'DB';
+ if ( $hafas
+ and $hafas ne '1'
+ and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $service = $hafas;
+ }
+
+ $self->render_later;
+
+ my @iris = map {
+ {
+ ds100 => $_->[0][0],
+ name => $_->[0][1],
+ eva => $_->[0][2],
+ lon => $_->[0][3],
+ lat => $_->[0][4],
+ distance => $_->[1],
+ hafas => 0,
+ }
+ } Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon,
+ $lat, 10 );
+
+ @iris = uniq_by { $_->{name} } @iris;
+
+ Travel::Status::DE::HAFAS->new_p(
+ promise => 'Mojo::Promise',
+ user_agent => $self->ua,
+ service => $service,
+ geoSearch => {
+ lat => $lat,
+ lon => $lon
+ }
+ )->then(
+ sub {
+ my ($hafas) = @_;
+ my @hafas = map {
+ {
+ name => $_->name,
+ eva => $_->eva,
+ distance => $_->distance_m / 1000,
+ hafas => $service,
+ }
+ } $hafas->results;
+ if ( @hafas > 10 ) {
+ @hafas = @hafas[ 0 .. 9 ];
}
- );
+ my @results = map { $_->[0] }
+ sort { $a->[1] <=> $b->[1] }
+ map { [ $_, $_->{distance} ] } ( @iris, @hafas );
+ $self->render(
+ json => {
+ candidates => [@results],
+ }
+ );
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->render(
+ json => {
+ candidates => [@iris],
+ warning => $err,
+ }
+ );
+ }
+ )->wait;
+}
+
+sub autocomplete {
+ my ($self) = @_;
+
+ $self->res->headers->cache_control('max-age=31536000, immutable');
+
+ my $output = '$(function(){const stations=';
+ $output
+ .= encode_json(
+ [ map { $_->[1] } Travel::Status::DE::IRIS::Stations::get_stations() ]
+ );
+ $output .= ";\n";
+ $output
+ .= "\$('input.station').autocomplete({delay:0,minLength:3,source:stations});});\n";
+
+ $self->render(
+ format => 'js',
+ data => $output
+ );
+}
+
+sub redirect_to_station {
+ my ($self) = @_;
+ my $input = $self->param('input');
+ my $params = $self->req->params;
+
+ $params->remove('input');
+
+ for my $param (qw(platforms mode admode via)) {
+ if (
+ not $params->param($param)
+ or ( exists $default{$param}
+ and $params->param($param) eq $default{$param} )
+ )
+ {
+ $params->remove($param);
+ }
+ }
+
+ if ( $input =~ m{ ^ [a-zA-Z]{1,5} \s+ \d+ }x ) {
+ if ( $input =~ s{ \s* @ \s* (?<date> [0-9.]+) $ }{}x ) {
+ $params->param( date => $+{date} );
+ }
+ elsif ( $input =~ s{ \s* [(] \s* (?<date> [0-9.]+) \s* [)] $ }{}x ) {
+ $params->param( date => $+{date} );
+ }
+ $params = $params->to_string;
+ $self->redirect_to("/z/${input}?${params}");
+ }
+ elsif ( $params->param('hafas') and $params->param('hafas') ne '1' ) {
+ $params = $params->to_string;
+ $self->redirect_to("/${input}?${params}");
+ }
+ else {
+ my @candidates
+ = Travel::Status::DE::IRIS::Stations::get_station($input);
+ if (
+ @candidates == 1
+ and ( $input eq $candidates[0][0]
+ or lc($input) eq lc( $candidates[0][1] )
+ or $input eq $candidates[0][2] )
+ )
+ {
+ $params->remove('hafas');
+ }
+ else {
+ $params->param( hafas => 1 );
+ }
+ $params = $params->to_string;
+ $self->redirect_to("/${input}?${params}");
}
}