summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/DBInfoscreen.pm84
-rw-r--r--lib/DBInfoscreen/Controller/Map.pm217
-rw-r--r--lib/DBInfoscreen/Controller/Static.pm31
-rw-r--r--lib/DBInfoscreen/Controller/Stationboard.pm1503
-rw-r--r--lib/DBInfoscreen/Controller/Wagenreihung.pm149
-rw-r--r--lib/DBInfoscreen/Helper/EFA.pm9
-rw-r--r--lib/DBInfoscreen/Helper/HAFAS.pm355
-rw-r--r--lib/DBInfoscreen/Helper/Wagonorder.pm163
-rw-r--r--lib/DBInfoscreen/I18N/en.pm84
9 files changed, 1539 insertions, 1056 deletions
diff --git a/lib/DBInfoscreen.pm b/lib/DBInfoscreen.pm
index 836f94e..1fd3674 100644
--- a/lib/DBInfoscreen.pm
+++ b/lib/DBInfoscreen.pm
@@ -1,6 +1,6 @@
package DBInfoscreen;
-# Copyright (C) 2011-2020 Daniel Friesel
+# Copyright (C) 2011-2020 Birte Kristina Friesel
#
# SPDX-License-Identifier: AGPL-3.0-or-later
@@ -12,12 +12,9 @@ use DBInfoscreen::Helper::HAFAS;
use DBInfoscreen::Helper::Wagonorder;
use File::Slurp qw(read_file);
use JSON;
-use Travel::Status::DE::IRIS::Stations;
use utf8;
-no if $] >= 5.018, warnings => 'experimental::smartmatch';
-
sub startup {
my ($self) = @_;
@@ -39,6 +36,7 @@ sub startup {
);
chomp $self->config->{version};
+ $self->defaults( version => $self->config->{version} // 'UNKNOWN' );
# Generally, the reverse proxy handles compression.
# Also, Mojolicious compression breaks legacy callback-based JSON endpoints
@@ -49,15 +47,14 @@ sub startup {
before_dispatch => sub {
my ($self) = @_;
- # The "theme" cookie is set client-side if the theme we delivered was
- # changed by dark mode detection or by using the theme switcher. It's
- # not part of Mojolicious' session data (and can't be, due to
- # signing and HTTPOnly), so we need to add it here.
+ # The "theme" cookie is set client-side if the theme we delivered was
+ # changed by dark mode detection or by using the theme switcher. It's
+ # not part of Mojolicious' session data (and can't be, due to
+ # signing and HTTPOnly), so we need to add it here.
for my $cookie ( @{ $self->req->cookies } ) {
if ( $cookie->name eq 'theme' ) {
$self->session( theme => $cookie->value );
- return;
}
}
}
@@ -88,36 +85,6 @@ sub startup {
);
$self->attr(
- ice_type_map => sub {
- if ( -r 'share/zugbildungsplan.json' ) {
- my $ice_type_map = JSON->new->utf8->decode(
- scalar read_file('share/zugbildungsplan.json') );
- my $ret = {};
- while ( my ( $k, $v ) = each %{ $ice_type_map->{train} } ) {
- if ( $v->{type} ) {
- $ret->{$k} = [
- $v->{type}, $v->{shortType},
- exists $v->{wagons} ? 1 : 0
- ];
- }
- }
- return $ret;
- }
- return {};
- }
- );
-
- $self->attr(
- train_details_db => sub {
- if ( -r 'share/zugbildungsplan.json' ) {
- return JSON->new->utf8->decode(
- scalar read_file('share/zugbildungsplan.json') )->{train};
- }
- return {};
- }
- );
-
- $self->attr(
dbdb_wagon => sub {
return JSON->new->utf8->decode(
scalar read_file('share/dbdb_wagen.json') );
@@ -220,9 +187,27 @@ sub startup {
my ( $self, $occupancy ) = @_;
my @symbols
- = (qw(help_outline person_outline people priority_high));
+ = (
+ qw(help_outline person_outline people priority_high not_interested)
+ );
my $text = 'Auslastung unbekannt';
+ if ( $occupancy eq 'MANY_SEATS' ) {
+ $occupancy = 1;
+ }
+ elsif ( $occupancy eq 'FEW_SEATS' ) {
+ $occupancy = 2;
+ }
+ elsif ( $occupancy eq 'STANDING_ONLY' ) {
+ $occupancy = 3;
+ }
+ elsif ( $occupancy eq 'FULL' ) {
+ $occupancy = 4;
+ }
+
+ if ( $occupancy > 3 ) {
+ $text = 'Voraussichtlich überfüllt';
+ }
if ( $occupancy > 2 ) {
$text = 'Sehr hohe Auslastung erwartet';
}
@@ -290,7 +275,7 @@ sub startup {
my $r = $self->routes;
- $r->get('/_redirect')->to('static#redirect');
+ $r->get('/_redirect')->to('stationboard#redirect_to_station');
# legacy entry point
$r->get('/_auto')->to('static#geostop');
@@ -305,24 +290,25 @@ sub startup {
$r->get('/_impressum')->to('static#imprint');
+ $r->get('/dyn/:av/autocomplete.js')->to('stationboard#autocomplete');
+
$r->get('/_wr/:train/:departure')->to('wagenreihung#wagenreihung');
- $r->get('/wr/:train')->to('wagenreihung#zugbildung_db');
$r->get('/w/*wagon')->to('wagenreihung#wagen');
$r->get('/_ajax_mapinfo/:tripid/:lineno')->to('map#ajax_route');
$r->get('/map/:tripid/:lineno')->to('map#route');
- $r->get('/intersection/:trips')->to('map#intersection');
- $r->get('/z/:train/*station')->to('stationboard#station_train_details');
- $r->get('/z/:train')->to('stationboard#train_details');
-
- $r->get('/map')->to('map#search_form');
- $r->get('/_trainsearch')->to('map#search');
+ $r->get( '/z/:train/*station' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'stationboard#station_train_details', format => undef )
+ ->name('train_at_station');
+ $r->get( '/z/:train' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'stationboard#train_details', format => undef )->name('train');
$self->defaults( layout => 'app' );
$r->get('/')->to('stationboard#handle_request');
$r->get('/multi/*station')->to('stationboard#handle_request');
- $r->get('/*station')->to('stationboard#handle_request');
+ $r->get( '/*station' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'stationboard#handle_request', format => undef );
$self->types->type( json => 'application/json; charset=utf-8' );
diff --git a/lib/DBInfoscreen/Controller/Map.pm b/lib/DBInfoscreen/Controller/Map.pm
index 63b8b40..bced612 100644
--- a/lib/DBInfoscreen/Controller/Map.pm
+++ b/lib/DBInfoscreen/Controller/Map.pm
@@ -1,6 +1,6 @@
package DBInfoscreen::Controller::Map;
-# Copyright (C) 2011-2020 Daniel Friesel
+# Copyright (C) 2011-2020 Birte Kristina Friesel
#
# SPDX-License-Identifier: AGPL-3.0-or-later
@@ -61,14 +61,14 @@ sub get_route_indexes {
# - position 4 seconds from now
# - ...
sub estimate_train_positions {
- my (%opt) = @_;
+ my ( $self, %opt ) = @_;
my $now = $opt{now};
- my $from_dt = $opt{from}{dep} // $opt{from}{arr};
- my $to_dt = $opt{to}{arr} // $opt{to}{dep};
- my $from_name = $opt{from}{name};
- my $to_name = $opt{to}{name};
+ my $from_dt = $opt{from}->dep // $opt{from}->arr;
+ my $to_dt = $opt{to}->arr // $opt{to}->dep;
+ my $from_name = $opt{from}->loc->name;
+ my $to_name = $opt{to}->loc->name;
my $route = $opt{route};
my $polyline = $opt{polyline};
@@ -135,16 +135,23 @@ sub estimate_train_positions {
}
}
else {
+ $self->log->debug(
+ "Did not find route indexes for $from_name → $to_name");
+ $self->log->debug(
+"Falling back to $opt{from}{lat} $opt{from}{lon} → $opt{to}{lat} $opt{to}{lon}"
+ );
for my $ratio (@completion_ratios) {
my $lat
- = $opt{from}{lat} + ( $opt{to}{lat} - $opt{from}{lat} ) * $ratio;
+ = $opt{from}->loc->lat
+ + ( $opt{to}->loc->lat - $opt{from}->loc->lat ) * $ratio;
my $lon
- = $opt{from}{lon} + ( $opt{to}{lon} - $opt{from}{lon} ) * $ratio;
+ = $opt{from}->loc->lon
+ + ( $opt{to}->loc->lon - $opt{from}->loc->lon ) * $ratio;
push( @train_positions, [ $lat, $lon ] );
}
return @train_positions;
}
- return [ $opt{to}{lat}, $opt{to}{lon} ];
+ return [ $opt{to}->loc->lat, $opt{to}->loc->lon ];
}
# Input:
@@ -172,17 +179,17 @@ sub estimate_train_positions2 {
for my $i ( 1 .. $#route ) {
if ( not $next_stop
- and ( $route[$i]{arr} // $route[$i]{dep} )
- and ( $route[ $i - 1 ]{dep} // $route[ $i - 1 ]{arr} )
- and $now > ( $route[ $i - 1 ]{dep} // $route[ $i - 1 ]{arr} )
- and $now < ( $route[$i]{arr} // $route[$i]{dep} ) )
+ and ( $route[$i]->arr // $route[$i]->dep )
+ and ( $route[ $i - 1 ]->dep // $route[ $i - 1 ]->arr )
+ and $now > ( $route[ $i - 1 ]->dep // $route[ $i - 1 ]->arr )
+ and $now < ( $route[$i]->arr // $route[$i]->dep ) )
{
# HAFAS does not provide delays for past stops
$self->backpropagate_delay( $route[ $i - 1 ], $route[$i] );
# (current position, future positons...) in 2 second steps
- @train_positions = estimate_train_positions(
+ @train_positions = $self->estimate_train_positions(
from => $route[ $i - 1 ],
to => $route[$i],
now => $now,
@@ -200,15 +207,15 @@ sub estimate_train_positions2 {
and $now <= ( $route[ $i - 1 ]{dep} // $route[ $i - 1 ]{arr} ) )
{
@train_positions
- = ( [ $route[ $i - 1 ]{lat}, $route[ $i - 1 ]{lon} ] );
+ = ( [ $route[ $i - 1 ]->loc->lat, $route[ $i - 1 ]->loc->lon ] );
$next_stop = {
type => 'present',
station => $route[ $i - 1 ],
};
}
$stop_distance_sum += $distance->distance_metal(
- $route[ $i - 1 ]{lat}, $route[ $i - 1 ]{lon},
- $route[$i]{lat}, $route[$i]{lon}
+ $route[ $i - 1 ]->loc->lat, $route[ $i - 1 ]->loc->lon,
+ $route[$i]->loc->lat, $route[$i]->loc->lon
) / 1000;
}
@@ -217,7 +224,7 @@ sub estimate_train_positions2 {
}
if ( @route and not $next_stop ) {
- @train_positions = ( [ $route[-1]{lat}, $route[-1]{lon} ] );
+ @train_positions = ( [ $route[-1]->loc->lat, $route[-1]->loc->lon ] );
$next_stop = {
type => 'present',
station => $route[-1]
@@ -240,12 +247,12 @@ sub route_to_ajax {
my @route_entries;
for my $stop (@stopovers) {
- my @stop_entries = ( $stop->{name} );
+ my @stop_entries = ( $stop->loc->name );
my $platform;
- if ( my $arr = $stop->{arr} and not $stop->{arr_cancelled} ) {
- my $delay = $stop->{arr_delay} // 0;
- $platform = $stop->{arr_platform};
+ if ( my $arr = $stop->arr and not $stop->arr_cancelled ) {
+ my $delay = $stop->arr_delay // 0;
+ $platform = $stop->platform;
push( @stop_entries, $arr->epoch, $delay );
}
@@ -253,9 +260,9 @@ sub route_to_ajax {
push( @stop_entries, q{}, q{} );
}
- if ( my $dep = $stop->{dep} and not $stop->{dep_cancelled} ) {
- my $delay = $stop->{dep_delay} // 0;
- $platform //= $stop->{dep_platform} // q{};
+ if ( my $dep = $stop->dep and not $stop->dep_cancelled ) {
+ my $delay = $stop->dep_delay // 0;
+ $platform //= $stop->platform // q{};
push( @stop_entries, $dep->epoch, $delay, $platform );
}
@@ -307,13 +314,26 @@ sub route {
my ($self) = @_;
my $trip_id = $self->stash('tripid');
my $line_no = $self->stash('lineno');
+ my $hafas = $self->param('hafas');
my $from_name = $self->param('from');
my $to_name = $self->param('to');
$self->render_later;
- $self->hafas->get_polyline_p( $trip_id, $line_no )->then(
+ my $service = 'DB';
+ if ( $hafas
+ and $hafas ne '1'
+ and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $service = $hafas;
+ }
+
+ $self->hafas->get_polyline_p(
+ id => $trip_id,
+ line => $line_no,
+ service => $service
+ )->then(
sub {
my ($journey) = @_;
@@ -338,51 +358,51 @@ sub route {
# Prepare from/to markers and name/time/delay overlays for stations
for my $stop (@route) {
- my @stop_lines = ( $stop->{name} );
+ my @stop_lines = ( $stop->loc->name );
- if ( $from_name and $stop->{name} eq $from_name ) {
+ if ( $from_name and $stop->loc->name eq $from_name ) {
push(
@markers,
{
- lon => $stop->{lon},
- lat => $stop->{lat},
- title => $stop->{name},
+ lon => $stop->loc->lon,
+ lat => $stop->loc->lat,
+ title => $stop->loc->name,
icon => 'goldIcon',
}
);
}
- if ( $to_name and $stop->{name} eq $to_name ) {
+ if ( $to_name and $stop->loc->name eq $to_name ) {
push(
@markers,
{
- lon => $stop->{lon},
- lat => $stop->{lat},
- title => $stop->{name},
+ lon => $stop->loc->lon,
+ lat => $stop->loc->lat,
+ title => $stop->loc->name,
icon => 'greenIcon',
}
);
}
- if ( $stop->{platform} ) {
- push( @stop_lines, 'Gleis ' . $stop->{platform} );
+ if ( $stop->platform ) {
+ push( @stop_lines, 'Gleis ' . $stop->platform );
}
- if ( $stop->{arr} ) {
- my $arr_line = $stop->{arr}->strftime('Ankunft: %H:%M');
- if ( $stop->{arr_delay} ) {
- $arr_line .= sprintf( ' (%+d)', $stop->{arr_delay} );
+ if ( $stop->arr ) {
+ my $arr_line = $stop->arr->strftime('Ankunft: %H:%M');
+ if ( $stop->arr_delay ) {
+ $arr_line .= sprintf( ' (%+d)', $stop->arr_delay );
}
push( @stop_lines, $arr_line );
}
- if ( $stop->{dep} ) {
- my $dep_line = $stop->{dep}->strftime('Abfahrt: %H:%M');
- if ( $stop->{dep_delay} ) {
- $dep_line .= sprintf( ' (%+d)', $stop->{dep_delay} );
+ if ( $stop->dep ) {
+ my $dep_line = $stop->dep->strftime('Abfahrt: %H:%M');
+ if ( $stop->dep_delay ) {
+ $dep_line .= sprintf( ' (%+d)', $stop->dep_delay );
}
push( @stop_lines, $dep_line );
}
push( @station_coordinates,
- [ [ $stop->{lat}, $stop->{lon} ], [@stop_lines], ] );
+ [ [ $stop->loc->lat, $stop->loc->lon ], [@stop_lines], ] );
}
push(
@@ -397,6 +417,7 @@ sub route {
$self->render(
'route_map',
+ description => "Karte für " . $journey->name,
title => $journey->name,
hide_opts => 1,
with_map => 1,
@@ -405,12 +426,12 @@ sub route {
ajax_polyline => join( '|',
map { join( ';', @{$_} ) } @{ $train_pos->{positions} } ),
origin => {
- name => $journey->route_start,
- ts => ( $journey->route )[0]->{dep},
+ name => ( $journey->route )[0]->loc->name,
+ ts => ( $journey->route )[0]->dep,
},
destination => {
name => $journey->route_end,
- ts => ( $journey->route )[-1]->{arr},
+ ts => ( $journey->route )[-1]->arr,
},
train_no => $journey->number
? ( $journey->type . ' ' . $journey->number )
@@ -450,12 +471,25 @@ sub ajax_route {
my ($self) = @_;
my $trip_id = $self->stash('tripid');
my $line_no = $self->stash('lineno');
+ my $hafas = $self->param('hafas');
delete $self->stash->{layout};
$self->render_later;
- $self->hafas->get_polyline_p( $trip_id, $line_no )->then(
+ my $service = 'DB';
+ if ( $hafas
+ and $hafas ne '1'
+ and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $service = $hafas;
+ }
+
+ $self->hafas->get_polyline_p(
+ id => $trip_id,
+ line => $line_no,
+ service => $service
+ )->then(
sub {
my ($journey) = @_;
@@ -477,13 +511,16 @@ sub ajax_route {
ajax_polyline => join( '|',
map { join( ';', @{$_} ) } @{ $train_pos->{positions} } ),
origin => {
- name => $journey->route_start,
- ts => ( $journey->route )[0]->{dep},
+ name => ( $journey->route )[0]->loc->name,
+ ts => ( $journey->route )[0]->dep,
},
destination => {
name => $journey->route_end,
- ts => ( $journey->route )[-1]->{arr},
+ ts => ( $journey->route )[-1]->arr,
},
+ train_no => $journey->number
+ ? ( $journey->type . ' ' . $journey->number )
+ : undef,
next_stop => $train_pos->{next_stop},
);
}
@@ -498,78 +535,4 @@ sub ajax_route {
)->wait;
}
-sub search {
- my ($self) = @_;
-
- my $t1 = $self->param('train1');
- my $t2 = $self->param('train2');
-
- my $t1_data;
- my $t2_data;
-
- my @requests;
-
- if ( not( $t1 and $t1 =~ m{^\S+\s+\d+$} )
- or ( $t2 and not $t2 =~ m{^\S+\s+\d+$} ) )
- {
- $self->render(
- 'trainsearch',
- title => 'Fahrtverlauf',
- hide_opts => 1,
- error => $t1
- ? "Züge müssen im Format 'Zugtyp Nummer' angegeben werden, z.B. 'RE 1234'"
- : undef,
- );
- return;
- }
-
- $self->render_later;
-
- push( @requests, $self->hafas->trainsearch_p( train_no => $t1 ) );
-
- if ($t2) {
- push( @requests, $self->hafas->trainsearch_p( train_no => $t2 ) );
- }
-
- Mojo::Promise->all(@requests)->then(
- sub {
- my ( $t1_data, $t2_data ) = @_;
-
- if ($t2_data) {
- $self->redirect_to(
- sprintf(
- "/intersection/%s,0;%s,0",
- $t1_data->[0]{trip_id},
- $t2_data->[0]{trip_id},
- )
- );
- }
- else {
- $self->redirect_to(
- sprintf( "/map/%s/0", $t1_data->[0]{trip_id}, ) );
- }
- }
- )->catch(
- sub {
- my ($err) = @_;
- $self->render(
- 'trainsearch',
- title => 'Fahrtverlauf',
- hide_opts => 1,
- error => $err
- );
- }
- )->wait;
-}
-
-sub search_form {
- my ($self) = @_;
-
- $self->render(
- 'trainsearch',
- title => 'Fahrtverlauf',
- hide_opts => 1,
- );
-}
-
1;
diff --git a/lib/DBInfoscreen/Controller/Static.pm b/lib/DBInfoscreen/Controller/Static.pm
index e30b34f..927bf6e 100644
--- a/lib/DBInfoscreen/Controller/Static.pm
+++ b/lib/DBInfoscreen/Controller/Static.pm
@@ -1,6 +1,6 @@
package DBInfoscreen::Controller::Static;
-# Copyright (C) 2011-2020 Daniel Friesel
+# Copyright (C) 2011-2020 Birte Kristina Friesel
#
# SPDX-License-Identifier: AGPL-3.0-or-later
@@ -11,34 +11,6 @@ my %default = (
admode => 'deparr',
);
-sub redirect {
- 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);
- }
- }
-
- $params = $params->to_string;
-
- if ( $input =~ m{ ^ [a-zA-Z]{1,5} \s+ \d+ $ }x ) {
- $self->redirect_to("/z/${input}?${params}");
- }
- else {
- $self->redirect_to("/${input}?${params}");
- }
-}
-
sub geostop {
my ($self) = @_;
@@ -55,7 +27,6 @@ sub about {
$self->render(
'about',
hide_opts => 1,
- version => $self->config->{version}
);
}
diff --git a/lib/DBInfoscreen/Controller/Stationboard.pm b/lib/DBInfoscreen/Controller/Stationboard.pm
index 36f62d7..93388fa 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
@@ -11,28 +11,108 @@ use DateTime::Format::Strptime;
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::EFA;
+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, $efa ) = @_;
my $errstr = $data->{errstr};
+ if ($efa) {
+ $self->render(
+ 'landingpage',
+ error => ( $errstr // "Keine Abfahrten an '$station'" ),
+ hide_opts => 0,
+ status => $data->{status} // 404,
+ );
+ return;
+ }
+ elsif ($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 +126,7 @@ sub handle_no_results {
'landingpage',
stationlist => \@candidates,
hide_opts => 0,
- status => 300,
+ status => $data->{status} // 300,
);
return;
}
@@ -56,14 +136,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 +161,6 @@ sub handle_no_results_json {
if ($errstr) {
$json = {
api_version => $api_version,
- version => $self->config->{version},
error => $errstr,
};
}
@@ -91,7 +172,6 @@ sub handle_no_results_json {
{
$json = {
api_version => $api_version,
- version => $self->config->{version},
error => 'ambiguous station code/name',
candidates => \@candidates,
};
@@ -99,7 +179,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 +187,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 +241,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 +268,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 +297,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 +350,47 @@ 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{efa} ) {
+ my $service = 'VRR';
+ if ( $opt{efa} ne '1'
+ and Travel::Status::DE::EFA::get_service( $opt{efa} ) )
+ {
+ $service = $opt{efa};
+ }
+ return Travel::Status::DE::EFA->new_p(
+ service => $service,
+ name => $station,
+ lwp_options => {
+ timeout => 10,
+ agent => 'dbf.finalrewind.org/2'
+ },
+ promise => 'Mojo::Promise',
+ user_agent => Mojo::UserAgent->new,
+ );
+ }
+ 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 +401,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 +440,15 @@ sub handle_request {
my $station = $self->stash('station');
my $template = $self->param('mode') // 'app';
+ my $efa = $self->param('efa');
+ 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},
+ efa => $efa,
+ hafas => $hafas,
);
if ( $self->param('past') ) {
@@ -321,13 +457,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 +499,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 +525,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 +534,25 @@ sub handle_request {
$self->render_later;
- get_results_p( $station, %opt )->then(
+ $self->get_results_p( $station, %opt )->then(
sub {
my ($status) = @_;
+ if ($efa) {
+ $self->handle_efa( $station, $status );
+ return;
+ }
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 +562,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 +571,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, $efa
+ );
return;
}
)->wait;
@@ -529,19 +704,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,15 +731,35 @@ 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;
@@ -576,10 +773,67 @@ sub render_train {
= ( $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 { $_->desc_short }
+ grep { $_->desc_short } $wr->groups );
+ my $first = 0;
+ for my $group ( $wr->groups ) {
+ my $had_entry = 0;
+ for my $wagon ( $group->wagons ) {
+ if (
+ not( $wagon->is_locomotive
+ or $wagon->is_powercar )
+ )
+ {
+ my $class;
+ if ($first) {
+ push(
+ @{ $departure->{wr_preview} },
+ [ '•', 'meta' ]
+ );
+ $first = 0;
+ }
+ my $entry;
+ if ( $wagon->is_closed ) {
+ $entry = 'X';
+ $class = 'closed';
+ }
+ else {
+ $entry = $wagon->number
+ || (
+ $wagon->type =~ m{AB} ? '½'
+ : $wagon->type =~ m{A} ? '1.'
+ : $wagon->type =~ m{B} ? '2.'
+ : $wagon->type
+ );
+ }
+ if (
+ $group->train_no ne $departure->{train_no} )
+ {
+ $class = 'otherno';
+ }
+ push(
+ @{ $departure->{wr_preview} },
+ [ $entry, $class ]
+ );
+ $had_entry = 1;
+ }
+ }
+ if ($had_entry) {
+ $first = 1;
+ }
+ }
+ };
+ $departure->{wr_text} ||= 'Wagen';
return;
},
sub {
@@ -653,10 +907,11 @@ sub render_train {
}
if ($direction) {
- $departure->{direction} = $direction;
+ $departure->{wr_direction} = $direction;
+ $departure->{wr_direction_num} = $direction eq 'l' ? 0 : 100;
}
elsif ( $platform_info->{direction} ) {
- $departure->{direction} = 'a' . $platform_info->{direction};
+ $departure->{wr_direction} = 'a' . $platform_info->{direction};
}
return;
@@ -672,87 +927,58 @@ sub render_train {
}
)->wait;
- $self->hafas->get_route_timestamps_p( train => $result )->then(
- sub {
- my ( $route_ts, $journey ) = @_;
+ my %opt = ( train => $result );
- $departure->{trip_id} = $journey->id;
- $departure->{operator} = $journey->operator;
+ #if ( $self->languages =~ m{^en} ) {
+ # $opt{language} = 'en';
+ #}
- if ( my $load = $route_ts->{$station_name}{load} ) {
- if ( %{$load} ) {
- $departure->{utilization}
- = [ $load->{FIRST}, $load->{SECOND} ];
+ $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} ];
+ }
+ }
+ $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 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.
- my @hafas_stations = $journey->route;
- if ( my @iris_stations = @{ $departure->{route_pre_diff} } ) {
- my @missing_pre;
- for my $station (@hafas_stations) {
- if (
- List::MoreUtils::any { $_->{name} eq $station->{name} }
- @iris_stations
- )
- {
- unshift(
- @{ $departure->{route_pre_diff} },
- @missing_pre
- );
- last;
- }
+ if ( defined $split ) {
+ for my $i ( 0 .. $split - 1 ) {
push(
- @missing_pre,
- {
- name => $station->{name},
- hafas => 1
- }
+ @{ $departure->{route_pre_diff} },
+ shift( @{ $departure->{route_post_diff} } )
);
}
- }
- if ( my @iris_stations = @{ $departure->{route_post_diff} } ) {
- my @missing_post;
- for my $station ( reverse @hafas_stations ) {
- if (
- List::MoreUtils::any { $_->{name} eq $station->{name} }
- @iris_stations
- )
- {
- push(
- @{ $departure->{route_post_diff} },
- @missing_post
- );
- last;
- }
- unshift(
- @missing_post,
- {
- name => $station->{name},
- hafas => 1
- }
- );
- }
- }
-
- if ($route_ts) {
- if ( $route_ts->{ $result->station }{rt_bogus} ) {
- #$departure->{missing_realtime} = 1;
- }
- 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};
- }
- }
+ # remove entry for $station_name
+ shift( @{ $departure->{route_post_diff} } );
}
my @him_messages;
@@ -795,59 +1021,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');
@@ -857,6 +1069,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,
@@ -879,9 +1095,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)
@@ -921,6 +1148,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 => [
@@ -933,6 +1162,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}
@@ -949,22 +1180,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
@@ -972,16 +1212,13 @@ sub train_details {
delete $self->stash->{layout};
}
- my $api_version = $Travel::Status::DE::IRIS::VERSION;
-
$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 => [],
@@ -989,44 +1226,155 @@ 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_type => $train_type,
- train_no => $train_no
- )->then(
+ $self->hafas->get_route_p(%opt)->then(
sub {
- my ( $route_ts, $journey ) = @_;
+ my ( $route, $journey, $hafas_obj ) = @_;
$res->{trip_id} = $journey->id;
+ $res->{date} = $route->[0]{sched_dep} // $route->[0]{dep};
- if ( not defined $journey->class ) {
- $linetype = 'ext';
- }
- elsif ( $journey->class <= 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 ( $journey->class <= 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 ( $journey->class <= 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} = $journey->route_start;
$res->{destination} = $journey->route_end;
- $res->{operator} = $journey->operator;
+ $res->{operators} = [ $journey->operators ];
+
+ $res->{route_post_diff} = $route;
- $res->{route_post_diff}
- = [ map { { name => $_->{name} } } $journey->route ];
- 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};
}
}
@@ -1062,33 +1410,144 @@ sub train_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' ),
+ $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',
- message => $e,
- exception => undef,
- 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;
}
+sub handle_efa {
+ my ( $self, $station_name, $efa ) = @_;
+ my $template = $self->param('mode') // 'app';
+ my $hide_low_delay = $self->param('hidelowdelay') // 0;
+ my $hide_opts = $self->param('hide_opts') // 0;
+ my $show_realtime = $self->param('rt') // $self->param('show_realtime')
+ // 0;
+
+ my @departures;
+
+ if ( $self->param('ajax') ) {
+ delete $self->stash->{layout};
+ }
+
+ for my $result ( $efa->results ) {
+ my $time;
+
+ if ( $show_realtime and $result->rt_datetime ) {
+ $time = $result->rt_datetime->strftime('%H:%M');
+ }
+ else {
+ $time = $result->sched_datetime->strftime('%H:%M');
+ }
+
+ my $linetype = $result->mot_name // 'bahn';
+ if ( $linetype eq 's-bahn' ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( $linetype eq 'u-bahn' ) {
+ $linetype = 'ubahn';
+ }
+ elsif ( $linetype =~ m{bus} ) {
+ $linetype = 'bus';
+ }
+ elsif ( $linetype eq 'zug' ) {
+ $linetype = 'bahn';
+ }
+ elsif ( $linetype eq 'sonstige' ) {
+ $linetype = 'ext';
+ }
+ push(
+ @departures,
+ {
+ time => $time,
+ sched_departure => $result->sched_datetime->strftime('%H:%M'),
+ departure => $result->rt_datetime
+ ? $result->rt_datetime->strftime('%H:%M')
+ : undef,
+ train => $result->line,
+ train_type => q{},
+ train_line => $result->line,
+ train_no => $result->train_no,
+ via => [],
+ destination => $result->destination,
+ platform => $result->platform,
+ is_cancelled => $result->is_cancelled,
+ linetype => $linetype,
+ delay => $result->delay,
+ occupancy => $result->occupancy,
+ replaced_by => [],
+ replacement_for => [],
+ route_pre => [],
+ route_post => [],
+ wr_link => undef,
+ }
+ );
+ }
+
+ $self->render(
+ $template,
+ description => "Abfahrtstafel $station_name",
+ departures => \@departures,
+ station => $station_name,
+ version => $self->config->{version},
+ title => $station_name,
+ refresh_interval => $template eq 'app' ? 0 : 120,
+ hide_opts => $hide_opts,
+ hide_low_delay => $hide_low_delay,
+ show_realtime => $show_realtime,
+ load_marquee => (
+ $template eq 'single'
+ or $template eq 'multi'
+ ),
+ force_mobile => ( $template eq 'app' ),
+ );
+}
+
sub handle_result {
my ( $self, $data ) = @_;
@@ -1106,6 +1565,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' );
@@ -1116,13 +1577,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;
@@ -1137,7 +1597,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 )
@@ -1151,44 +1614,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');
@@ -1208,8 +1704,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' ) {
@@ -1225,29 +1727,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 {
{
@@ -1265,104 +1874,83 @@ 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,
+ load => $result->load // {},
+ 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') );
@@ -1408,10 +1996,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,
@@ -1424,8 +2038,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;
@@ -1434,29 +2053,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}");
}
}
diff --git a/lib/DBInfoscreen/Controller/Wagenreihung.pm b/lib/DBInfoscreen/Controller/Wagenreihung.pm
index b7c6d84..03a607d 100644
--- a/lib/DBInfoscreen/Controller/Wagenreihung.pm
+++ b/lib/DBInfoscreen/Controller/Wagenreihung.pm
@@ -1,6 +1,6 @@
package DBInfoscreen::Controller::Wagenreihung;
-# Copyright (C) 2011-2020 Daniel Friesel
+# Copyright (C) 2011-2020 Birte Kristina Friesel
#
# SPDX-License-Identifier: AGPL-3.0-or-later
@@ -13,120 +13,20 @@ use utf8;
use Travel::Status::DE::DBWagenreihung;
use Travel::Status::DE::DBWagenreihung::Wagon;
-sub get_zugbildung_db {
- my ( $self, $train_no ) = @_;
-
- say $train_no;
-
- my $details = $self->app->train_details_db->{$train_no};
-
- if ( not $details ) {
- return;
- }
-
- my @wagons;
-
- for my $wagon ( @{ $details->{wagons} } ) {
- my $wagon_type = $wagon->{type};
- my $wagon_number = $wagon->{number};
- my %wagon = (
- fahrzeugnummer => "",
- fahrzeugtyp => $wagon_type,
- kategorie => $wagon_type =~ m{^[0-9.]+$} ? 'LOK' : '',
- train_no => $train_no,
- wagenordnungsnummer => $wagon_number,
- positionamhalt => {
- startprozent => 0,
- endeprozent => 0,
- startmeter => 0,
- endemeter => 0,
- }
- );
- my $wagon = Travel::Status::DE::DBWagenreihung::Wagon->new(%wagon);
-
- if ( $details->{type} ) {
- $wagon->set_traintype( $details->{type} );
- }
- push( @wagons, $wagon );
- }
-
- my $pos = 0;
- for my $wagon (@wagons) {
- $wagon->{position}{start_percent} = $pos;
- $wagon->{position}{end_percent} = $pos + 5;
- $pos += 5;
- }
-
- my $train_type = $details->{rawType};
- $train_type =~ s{ - .* }{}x;
-
- my $route_start = $details->{route}{start} // $details->{route}{preStart};
- my $route_end = $details->{route}{end} // $details->{route}{postEnd};
- my $route = "${route_start} → ${route_end}";
-
- return {
- route => $route,
- train_type => $train_type,
- wagons => [@wagons]
- };
-}
-
-sub zugbildung_db {
- my ($self) = @_;
-
- my $train_no = $self->param('train');
-
- my $details = $self->get_zugbildung_db($train_no);
-
- if ( not $details ) {
- $self->render( 'not_found',
- message => "Keine Daten zu Zug ${train_no} bekannt" );
- return;
- }
+sub handle_wagenreihung_error {
+ my ( $self, $train_no, $err ) = @_;
$self->render(
- 'zugbildung_db',
- wr_error => undef,
- title => $details->{train_type} . ' ' . $train_no,
- route => $details->{route},
- zb => $details,
+ 'wagenreihung',
+ title => "Zug $train_no",
+ wr_error => $err,
train_no => $train_no,
- wagons => $details->{wagons},
+ wr => undef,
+ wref => undef,
hide_opts => 1,
);
}
-sub handle_wagenreihung_error {
- my ( $self, $train_no, $err ) = @_;
-
- my $details = $self->get_zugbildung_db($train_no);
- if ( $details and @{ $details->{wagons} } ) {
- my $wr_error
- = "${err}. Ersatzweise werden die Solldaten laut Fahrplan angezeigt.";
- $self->render(
- 'zugbildung_db',
- wr_error => $wr_error,
- title => $details->{train_type} . ' ' . $train_no,
- route => $details->{route},
- zb => $details,
- train_no => $train_no,
- wagons => $details->{wagons},
- hide_opts => 1,
- );
- }
- else {
- $self->render(
- 'wagenreihung',
- title => "Zug $train_no",
- wr_error => $err,
- train_no => $train_no,
- wr => undef,
- wref => undef,
- hide_opts => 1,
- );
- }
-}
-
sub wagenreihung {
my ($self) = @_;
my $train = $self->stash('train');
@@ -172,7 +72,7 @@ sub wagenreihung {
e => $exit_side ? substr( $exit_side, 0, 1 ) : '',
tt => $wr->train_type,
tn => $train,
- s => $wr->station_name,
+ s => $wr->station->{name},
p => $wr->platform
};
@@ -218,10 +118,10 @@ sub wagenreihung {
and $wnb2 =~ m{^\d+$} )
{
- # We need to perform normalization in two cases:
- # * wagon 1 is leftmost and its number is higher than wagon 2
- # * wagon 1 is rightmost and its number is lower than wagon 2
- # (-> the leftmost wagon has the highest number)
+ # We need to perform normalization in two cases:
+ # * wagon 1 is leftmost and its number is higher than wagon 2
+ # * wagon 1 is rightmost and its number is lower than wagon 2
+ # (-> the leftmost wagon has the highest number)
# However, if wpa/wna und wpb/wnb do not match, we have a
# winged train with different normalization requirements
@@ -261,11 +161,17 @@ sub wagenreihung {
$wref = b64_encode( encode_json($wref) );
+ my $title = join( ' / ',
+ map { $wr->train_type . ' ' . $_ } $wr->train_numbers );
+
$self->render(
'wagenreihung',
- wr_error => undef,
- title => join( ' / ',
- map { $wr->train_type . ' ' . $_ } $wr->train_numbers ),
+ description => sprintf(
+ 'Ist-Wagenreihung %s in %s',
+ $title, $wr->station->{name}
+ ),
+ wr_error => undef,
+ title => $title,
train_no => $train,
wr => $wr,
wref => $wref,
@@ -278,7 +184,7 @@ sub wagenreihung {
my ($err) = @_;
$self->handle_wagenreihung_error( $train,
- $err->{error}->{msg} // "Unbekannter Fehler" );
+ $err->{error}->{msg} // $err // "Unbekannter Fehler" );
return;
}
)->wait;
@@ -319,15 +225,15 @@ sub wagen {
);
}
- my $title = "Wagen $wagon_id";
+ my $title = 'Wagen ' . $wagon_id;
if ( $wref->{tt} and $wref->{tn} ) {
$title = sprintf( '%s %s', $wref->{tt}, $wref->{tn} );
if ($wagon_no) {
- $title .= " Wagen $wagon_no";
+ $title .= ' Wagen ' . $wagon_no;
}
else {
- $title .= " Wagen $wagon_id";
+ $title .= ' Wagen ' . $wagon_id;
}
}
@@ -351,6 +257,9 @@ sub wagen {
$self->render(
'wagen',
+ description => ( $wref->{s} ? 'Position von ' : q{} )
+ . $title
+ . ( $wref->{s} ? " in $wref->{s}" : q{} ),
title => $title,
wagon_files => [@wagon_files],
wagon_data => $self->app->dbdb_wagon->{$wagon_id},
diff --git a/lib/DBInfoscreen/Helper/EFA.pm b/lib/DBInfoscreen/Helper/EFA.pm
index 2386ebb..4e81bc3 100644
--- a/lib/DBInfoscreen/Helper/EFA.pm
+++ b/lib/DBInfoscreen/Helper/EFA.pm
@@ -1,6 +1,6 @@
package DBInfoscreen::Helper::EFA;
-# Copyright (C) 2020-2022 Daniel Friesel
+# Copyright (C) 2020-2022 Birte Kristina Friesel
#
# SPDX-License-Identifier: AGPL-3.0-or-later
@@ -13,7 +13,6 @@ use Encode qw(decode encode);
use Mojo::JSON qw(decode_json);
use Mojo::Promise;
use Mojo::Util qw(url_escape);
-use XML::LibXML;
sub new {
my ( $class, %opt ) = @_;
@@ -49,8 +48,7 @@ sub get_json_p {
if ( my $err = $tx->error ) {
$self->{log}->debug(
-"efa->get_json_p($url): HTTP $err->{code} $err->{message}"
- );
+ "efa->get_json_p($url): HTTP $err->{code} $err->{message}");
$cache->freeze( $url, { error => $err->{message} } );
$promise->reject(
"GET $url returned HTTP $err->{code} $err->{message}");
@@ -60,8 +58,7 @@ sub get_json_p {
my $res = $tx->res->json;
if ( not $res ) {
- $self->{log}
- ->debug("efa->get_json_p($url): empty response");
+ $self->{log}->debug("efa->get_json_p($url): empty response");
$promise->reject("GET $url returned empty response");
return;
}
diff --git a/lib/DBInfoscreen/Helper/HAFAS.pm b/lib/DBInfoscreen/Helper/HAFAS.pm
index 6af47e3..a55f03f 100644
--- a/lib/DBInfoscreen/Helper/HAFAS.pm
+++ b/lib/DBInfoscreen/Helper/HAFAS.pm
@@ -1,6 +1,6 @@
package DBInfoscreen::Helper::HAFAS;
-# Copyright (C) 2011-2022 Daniel Friesel
+# Copyright (C) 2011-2022 Birte Kristina Friesel
#
# SPDX-License-Identifier: AGPL-3.0-or-later
@@ -13,7 +13,6 @@ use Encode qw(decode encode);
use Travel::Status::DE::HAFAS;
use Mojo::JSON qw(decode_json);
use Mojo::Promise;
-use XML::LibXML;
sub new {
my ( $class, %opt ) = @_;
@@ -29,224 +28,210 @@ sub new {
}
-sub get_json_p {
- my ( $self, $cache, $url ) = @_;
+sub get_route_p {
+ my ( $self, %opt ) = @_;
my $promise = Mojo::Promise->new;
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
- if ( my $content = $cache->thaw($url) ) {
- return $promise->resolve($content);
+ my $hafas_promise;
+
+ if ( $opt{trip_id} ) {
+ $hafas_promise = Travel::Status::DE::HAFAS->new_p(
+ service => $opt{service},
+ journey => {
+ id => $opt{trip_id},
+ },
+ language => $opt{language},
+ cache => $self->{realtime_cache},
+ promise => 'Mojo::Promise',
+ user_agent => $self->{user_agent}->request_timeout(10)
+ );
}
-
- $self->{log}->debug("get_json_p($url)");
-
- $self->{user_agent}->request_timeout(5)->get_p( $url => $self->{header} )
- ->then(
- sub {
- my ($tx) = @_;
-
- if ( my $err = $tx->error ) {
- $self->{log}->warn(
- "hafas->get_json_p($url): HTTP $err->{code} $err->{message}"
- );
- $promise->reject(
- "GET $url returned HTTP $err->{code} $err->{message}");
- return;
- }
- my $body
- = encode( 'utf-8', decode( 'ISO-8859-15', $tx->res->body ) );
-
- $body =~ s{^TSLs[.]sls = }{};
- $body =~ s{;$}{};
- $body =~ s{&#x0028;}{(}g;
- $body =~ s{&#x0029;}{)}g;
-
- my $json = decode_json($body);
-
- if ( not $json ) {
- $self->{log}->debug("hafas->get_json_p($url): empty response");
- $promise->reject("GET $url returned empty response");
- return;
- }
-
- $cache->freeze( $url, $json );
-
- $promise->resolve($json);
- return;
- }
- )->catch(
- sub {
- my ($err) = @_;
- $self->{log}->warn("hafas->get_json_p($url): $err");
- $promise->reject($err);
- return;
- }
- )->wait;
-
- return $promise;
-}
-
-sub trainsearch_p {
- my ( $self, %opt ) = @_;
-
- my $base
- = 'https://reiseauskunft.bahn.de/bin/trainsearch.exe/dn?L=vs_json&start=yes&rt=1';
-
- if ( not $opt{date_yy} ) {
- my $now = DateTime->now( time_zone => 'Europe/Berlin' );
- $opt{date_yy} = $now->strftime('%d.%m.%y');
- $opt{date_yyyy} = $now->strftime('%d.%m.%Y');
+ elsif ( $opt{train} ) {
+ $opt{train_req} = $opt{train}->type . ' ' . $opt{train}->train_no;
+ $opt{train_origin} = $opt{train}->origin;
+ }
+ else {
+ $opt{train_req} = $opt{train_type} . ' ' . $opt{train_no};
}
- # IRIS reports trains with unknown type as type "-". HAFAS thinks otherwise
- # and prefers the type to be left out entirely in this case.
- $opt{train_req} =~ s{^- }{};
-
- my $promise = Mojo::Promise->new;
-
- $self->get_json_p( $self->{realtime_cache},
- "${base}&date=$opt{date_yy}&trainname=$opt{train_req}" )->then(
+ $hafas_promise //= Travel::Status::DE::HAFAS->new_p(
+ journeyMatch => $opt{train_req} =~ s{^- }{}r,
+ datetime => ( $opt{train} ? $opt{train}->start : $opt{datetime} ),
+ language => $opt{language},
+ cache => $self->{realtime_cache},
+ promise => 'Mojo::Promise',
+ user_agent => $self->{user_agent}->request_timeout(10)
+ )->then(
sub {
- my ($trainsearch) = @_;
-
- # Fallback: Take first result
- my $result = $trainsearch->{suggestions}[0];
+ my ($hafas) = @_;
+ my @results = $hafas->results;
- # Try finding a result for the current date
- for my $suggestion ( @{ $trainsearch->{suggestions} // [] } ) {
+ if ( not @results ) {
+ return Mojo::Promise->reject(
+ "journeyMatch($opt{train_req}) found no results");
+ }
- # Drunken API, sail with care. Both date formats are used interchangeably
- if (
- exists $suggestion->{depDate}
- and ( $suggestion->{depDate} eq $opt{date_yy}
- or $suggestion->{depDate} eq $opt{date_yyyy} )
- )
- {
- # Train numbers are not unique, e.g. IC 149 refers both to the
- # InterCity service Amsterdam -> Berlin and to the InterCity service
- # Koebenhavns Lufthavn st -> Aarhus. One workaround is making
- # requests with the stationFilter=80 parameter. Checking the origin
- # station seems to be the more generic solution, so we do that
- # instead.
- if ( $opt{train_origin}
- and $suggestion->{dep} eq $opt{train_origin} )
+ my $result = $results[0];
+ if ( @results > 1 ) {
+ for my $journey (@results) {
+ if ( $opt{train_origin}
+ and ( $journey->route )[0]->loc->name eq
+ $opt{train_origin} )
{
- $result = $suggestion;
+ $result = $journey;
last;
}
}
}
- if ($result) {
-
- # The trip_id's date part doesn't seem to matter -- so far, HAFAS is
- # happy as long as the date part starts with a number. HAFAS-internal
- # tripIDs use this format (withouth leading zero for day of month < 10)
- # though, so let's stick with it.
- my $date_map = $opt{date_yyyy};
- $date_map =~ tr{.}{}d;
- $result->{trip_id} = sprintf( '1|%d|%d|%d|%s',
- $result->{id}, $result->{cycle},
- $result->{pool}, $date_map );
- $promise->resolve($result);
- }
- else {
- $self->{log}->warn(
- "hafas->trainsearch_p($opt{train_req}): train not found");
- $promise->reject("Zug $opt{train_req} nicht gefunden");
- }
-
- # do not propagate $promise->reject's return value to this promise.
- # Perl implicitly returns the last statement, so we explicitly return
- # nothing to avoid this.
- return;
- }
- )->catch(
- sub {
- my ($err) = @_;
- $self->{log}->warn("hafas->trainsearch_p($opt{train_req}): $err");
- $promise->reject($err);
-
- # do not propagate $promise->reject's return value to this promise
- return;
- }
- )->wait;
-
- return $promise;
-}
-
-sub get_route_timestamps_p {
- my ( $self, %opt ) = @_;
-
- my $promise = Mojo::Promise->new;
- my $now = DateTime->now( time_zone => 'Europe/Berlin' );
-
- if ( $opt{train} ) {
- $opt{date_yy} = $opt{train}->start->strftime('%d.%m.%y');
- $opt{date_yyyy} = $opt{train}->start->strftime('%d.%m.%Y');
- $opt{train_req} = $opt{train}->type . ' ' . $opt{train}->train_no;
- $opt{train_origin} = $opt{train}->origin;
- }
- else {
- $opt{train_req} = $opt{train_type} . ' ' . $opt{train_no};
- $opt{date_yy} = $now->strftime('%d.%m.%y');
- $opt{date_yyyy} = $now->strftime('%d.%m.%Y');
- }
-
- $self->trainsearch_p(%opt)->then(
- sub {
- my ($trainsearch_result) = @_;
- my $trip_id = $trainsearch_result->{trip_id};
return Travel::Status::DE::HAFAS->new_p(
journey => {
- id => $trip_id,
-
- # name => $opt{train_no},
+ id => $result->id,
},
+ language => $opt{language},
cache => $self->{realtime_cache},
promise => 'Mojo::Promise',
user_agent => $self->{user_agent}->request_timeout(10)
);
}
- )->then(
+ );
+
+ $hafas_promise->then(
sub {
my ($hafas) = @_;
my $journey = $hafas->result;
- my $ret = {};
-
+ my @ret;
my $station_is_past = 1;
+
+ my $num_names = 0;
+ my $prev_name = q{};
+ my $num_directions = 0;
+ my $prev_direction = q{};
+ my $num_operators = 0;
+ my $prev_operator = q{};
+
+ for my $stop ( $journey->route ) {
+ my $prod = $stop->prod_dep // $stop->prod_arr;
+ if ( $prod and $prod->name and $prod->name ne $prev_name ) {
+ $num_names++;
+ $prev_name = $prod->name;
+ }
+ if ( $prod
+ and $prod->operator
+ and $prod->operator ne $prev_operator )
+ {
+ $num_operators++;
+ $prev_operator = $prod->operator;
+ }
+ if ( $stop->direction and $stop->direction ne $prev_direction )
+ {
+ $num_directions++;
+ $prev_direction = $stop->direction;
+ }
+ }
+
+ $prev_name = q{};
+ $prev_direction = q{};
+ $prev_operator = q{};
+
for my $stop ( $journey->route ) {
- my $name = $stop->{name};
- $ret->{$name} = {
- sched_arr => $stop->{sched_arr},
- sched_dep => $stop->{sched_dep},
- rt_arr => $stop->{rt_arr},
- rt_dep => $stop->{rt_dep},
- arr_delay => $stop->{arr_delay},
- dep_delay => $stop->{dep_delay},
- load => $stop->{load},
- isCancelled => (
- ( $stop->{arr_cancelled} or not $stop->{sched_arr} )
- and
- ( $stop->{dep_cancelled} or not $stop->{sched_dep} )
- ),
- };
+
+ my $prod = $stop->prod_dep // $stop->prod_arr;
+ my %annotation;
+ if ( $num_names > 1
+ and $prod
+ and $prod->name
+ and $prod->name ne $prev_name )
+ {
+ $prev_name = $annotation{prod_name} = $prod->name;
+ }
+ if ( $num_operators > 1
+ and $prod
+ and $prod->operator
+ and $prod->operator ne $prev_operator )
+ {
+ $prev_operator = $annotation{operator} = $prod->operator;
+ }
+ if ( $num_directions > 1
+ and $stop->direction
+ and $stop->direction ne $prev_direction )
+ {
+ $prev_direction = $annotation{direction} = $stop->direction;
+ }
+
+ if (%annotation) {
+ $annotation{is_annotated} = 1;
+ }
+
+ push(
+ @ret,
+ {
+ name => $stop->loc->name,
+ eva => $stop->loc->eva,
+ sched_arr => $stop->sched_arr,
+ sched_dep => $stop->sched_dep,
+ rt_arr => $stop->rt_arr,
+ rt_dep => $stop->rt_dep,
+ arr_delay => $stop->arr_delay,
+ dep_delay => $stop->dep_delay,
+ arr_cancelled => $stop->arr_cancelled,
+ dep_cancelled => $stop->dep_cancelled,
+ tz_offset => $stop->tz_offset,
+ platform => $stop->platform,
+ sched_platform => $stop->sched_platform,
+ load => $stop->load,
+ isAdditional => $stop->is_additional,
+ isCancelled => (
+ ( $stop->arr_cancelled or not $stop->sched_arr )
+ and
+ ( $stop->dep_cancelled or not $stop->sched_dep )
+ ),
+ %annotation,
+ }
+ );
if (
$station_is_past
- and not $ret->{$name}{isCancelled}
+ and not $ret[-1]{isCancelled}
and $now->epoch < (
- $ret->{$name}{rt_arr} // $ret->{$name}{rt_dep}
- // $ret->{$name}{sched_arr}
- // $ret->{$name}{sched_dep} // $now
+ $ret[-1]{rt_arr} // $ret[-1]{rt_dep}
+ // $ret[-1]{sched_arr} // $ret[-1]{sched_dep} // $now
)->epoch
)
{
$station_is_past = 0;
}
- $ret->{$name}{isPast} = $station_is_past;
+ $ret[-1]{isPast} = $station_is_past;
+ if ( $stop->tz_offset ) {
+ if ( $stop->sched_arr ) {
+ $ret[-1]{local_sched_arr}
+ = $stop->sched_arr->clone->add(
+ minutes => $stop->tz_offset );
+ }
+ if ( $stop->sched_dep ) {
+ $ret[-1]{local_sched_dep}
+ = $stop->sched_dep->clone->add(
+ minutes => $stop->tz_offset );
+ }
+ if ( $stop->rt_arr ) {
+ $ret[-1]{local_rt_arr} = $stop->rt_arr->clone->add(
+ minutes => $stop->tz_offset );
+ }
+ if ( $stop->rt_dep ) {
+ $ret[-1]{local_rt_dep} = $stop->rt_dep->clone->add(
+ minutes => $stop->tz_offset );
+ }
+ $ret[-1]{local_dt_ad} = $ret[-1]{local_rt_arr}
+ // $ret[-1]{local_sched_arr} // $ret[-1]{local_rt_dep}
+ // $ret[-1]{local_sched_dep};
+ $ret[-1]{local_dt_da} = $ret[-1]{local_rt_dep}
+ // $ret[-1]{local_sched_dep} // $ret[-1]{local_rt_arr}
+ // $ret[-1]{local_sched_arr};
+ }
}
- $promise->resolve( $ret, $journey );
+ $promise->resolve( \@ret, $journey, $hafas );
return;
}
)->catch(
@@ -263,11 +248,15 @@ sub get_route_timestamps_p {
# Input: (HAFAS TripID, line number)
# Output: Promise returning a Travel::Status::DE::HAFAS::Journey instance on success
sub get_polyline_p {
- my ( $self, $trip_id, $line ) = @_;
+ my ( $self, %opt ) = @_;
+ my $trip_id = $opt{id};
+ my $line = $opt{line};
+ my $service = $opt{service};
my $promise = Mojo::Promise->new;
Travel::Status::DE::HAFAS->new_p(
+ service => $service,
journey => {
id => $trip_id,
name => $line,
diff --git a/lib/DBInfoscreen/Helper/Wagonorder.pm b/lib/DBInfoscreen/Helper/Wagonorder.pm
index 469eda6..5cdee40 100644
--- a/lib/DBInfoscreen/Helper/Wagonorder.pm
+++ b/lib/DBInfoscreen/Helper/Wagonorder.pm
@@ -1,6 +1,6 @@
package DBInfoscreen::Helper::Wagonorder;
-# Copyright (C) 2011-2020 Daniel Friesel
+# Copyright (C) 2011-2020 Birte Kristina Friesel
#
# SPDX-License-Identifier: AGPL-3.0-or-later
@@ -24,170 +24,11 @@ sub new {
}
-sub is_available_p {
- my ( $self, $train, $wr_link ) = @_;
- my $promise = Mojo::Promise->new;
-
- $self->check_wagonorder_p( $train->train_no, $wr_link )->then(
- sub {
- my ($body) = @_;
- $promise->resolve($body);
- return;
- },
- sub {
- if ( $train->is_wing ) {
- my $wing = $train->wing_of;
- return $self->check_wagonorder_p( $wing->train_no, $wr_link );
- }
- else {
- $promise->reject;
- return;
- }
- }
- )->then(
- sub {
- my ($body) = @_;
- $promise->resolve($body);
- return;
- },
- sub {
- $promise->reject;
- return;
- }
- )->wait;
-
- return $promise;
-}
-
-sub get_dbdb_p {
- my ( $self, $url ) = @_;
-
- my $promise = Mojo::Promise->new;
-
- my $cache = $self->{main_cache};
-
- if ( my $content = $cache->get($url) ) {
- if ($content) {
- return $promise->resolve($content);
- }
- else {
- return $promise->reject;
- }
- }
-
- $self->{user_agent}->request_timeout(5)->get_p( $url => $self->{header} )
- ->then(
- sub {
- my ($tx) = @_;
- if ( $tx->result->is_success ) {
- my $body = $tx->result->body;
- $cache->set( $url, $body );
- $promise->resolve($body);
- }
- else {
- $cache->set( $url, q{} );
- $promise->reject;
- }
- return;
- }
- )->catch(
- sub {
- $cache->set( $url, q{} );
- $promise->reject;
- return;
- }
- )->wait;
- return $promise;
-}
-
-sub head_dbdb_p {
- my ( $self, $url ) = @_;
-
- my $promise = Mojo::Promise->new;
-
- my $cache = $self->{main_cache};
-
- if ( my $content = $cache->get($url) ) {
- $self->{log}->debug("wagonorder->head_dbdb_p($url): cached ($content)");
- if ( $content eq 'y' ) {
- return $promise->resolve;
- }
- else {
- return $promise->reject;
- }
- }
-
- $self->{user_agent}->request_timeout(5)->head_p( $url => $self->{header} )
- ->then(
- sub {
- my ($tx) = @_;
- if ( $tx->result->is_success ) {
- $self->{log}->debug("wagonorder->head_dbdb_p($url): y");
- $cache->set( $url, 'y' );
- $promise->resolve;
- }
- else {
- $self->{log}->debug("wagonorder->head_dbdb_p($url): n");
- $cache->set( $url, 'n' );
- $promise->reject;
- }
- return;
- }
- )->catch(
- sub {
- $self->{log}->debug("wagonorder->head_dbdb_p($url): n");
- $cache->set( $url, 'n' );
- $promise->reject;
- return;
- }
- )->wait;
- return $promise;
-}
-
-sub has_cycle_p {
- my ( $self, $train_no ) = @_;
-
- return $self->head_dbdb_p(
- "https://lib.finalrewind.org/dbdb/db_umlauf/${train_no}.svg");
-}
-
-sub check_wagonorder_p {
- my ( $self, $train_no, $wr_link ) = @_;
-
- my $promise = Mojo::Promise->new;
-
- $self->head_dbdb_p(
- "https://lib.finalrewind.org/dbdb/has_wagonorder/${train_no}/${wr_link}"
- )->then(
- sub {
- $promise->resolve;
- return;
- }
- )->catch(
- sub {
- $self->get_p( $train_no, $wr_link )->then(
- sub {
- $promise->resolve;
- return;
- }
- )->catch(
- sub {
- $promise->reject;
- return;
- }
- )->wait;
- return;
- }
- )->wait;
-
- return $promise;
-}
-
sub get_p {
my ( $self, $train_no, $api_ts ) = @_;
my $url
- = "https://www.apps-bahn.de/wr/wagenreihung/1.0/${train_no}/${api_ts}";
+ = "https://ist-wr.noncd.db.de/wagenreihung/1.0/${train_no}/${api_ts}";
my $cache = $self->{realtime_cache};
diff --git a/lib/DBInfoscreen/I18N/en.pm b/lib/DBInfoscreen/I18N/en.pm
new file mode 100644
index 0000000..3abb70f
--- /dev/null
+++ b/lib/DBInfoscreen/I18N/en.pm
@@ -0,0 +1,84 @@
+package DBInfoscreen::I18N::en;
+
+# Copyright (C) 2023 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+use Mojo::Base 'DBInfoscreen::I18N';
+
+our %Lexicon = (
+
+ # common
+ 'Stationen in der Umgebung suchen' => 'Find stops nearby',
+
+ # layouts/app
+ 'Mehrdeutige Eingabe' => 'Ambiguous input',
+ 'Bitte eine Station aus der Liste auswählen' =>
+ 'Please select a station from the list',
+ 'Zug / Station' => 'Enter train number or station name',
+ 'Zug, Stationsname oder Ril100-Kürzel' =>
+ 'train, station name, or DS100 code',
+ 'Abfahrtstafel' => 'Show departures',
+ 'Weitere Einstellungen' => 'Preferences',
+ 'Zeiten inkl. Verspätung angeben' => 'Include delay in timestamps',
+ 'Verspätungen erst ab 5 Minuten anzeigen' => 'Hide delays below 5 minutes',
+ 'Mehr Details' => 'Verbose mode',
+'Betriebliche Bahnhofstrennungen berücksichtigen (z.B. "Hbf (Fern+Regio)" vs. "Hbf (S)")'
+ => 'Respect split stations; do not join them',
+ 'Bereits abgefahrene Züge anzeigen' => 'Include past trains',
+ 'Formular verstecken' => 'Hide form',
+ 'Nur Züge über' => 'Only show trains via',
+ 'Bahnhof 1, Bhf2, ... (oder regulärer Ausdruck)' =>
+ 'Station 1, 2, ... (or regular expression)',
+ 'Gleise' => 'Platforms',
+ 'Ankunfts- oder Abfahrtszeit anzeigen?' => 'Show arrival or departure?',
+ 'Abfahrt bevorzugen' => 'prefer departure',
+ 'Nur Abfahrt' => 'departure only',
+ 'Nur Ankunft' => 'arrival only',
+ 'Anzeigen' => 'Submit',
+ 'Datenschutz' => 'Privacy',
+ 'Impressum' => 'Imprint',
+
+ # landing page
+ 'Oder hier angeben:' => 'Or enter manually:',
+
+ # train details
+ 'Gleis' => 'Platform',
+ 'An:' => 'Arr',
+ 'Ab:' => 'Dep',
+ 'Plan:' => 'Sched',
+ 'Auslastung unbekannt' => 'Occupancy unknown',
+ 'Geringe Auslastung' => 'Low occupancy',
+ 'Hohe Auslastung' => 'High occupancy',
+ 'Sehr hohe Auslastung' => 'Very high occupancy',
+ 'Zug ist ausgebucht' => 'Fully booked',
+ 'Geringe Auslastung erwartet' => 'Low occupancy expected',
+ 'Hohe Auslastung erwartet' => 'High occupancy expected',
+ 'Sehr hohe Auslastung erwartet' => 'Very high occupancy expected',
+ 'Meldungen' => 'Messages',
+ 'Fahrtverlauf am' => 'Route on',
+ 'Betrieb' => 'Operator',
+ 'Karte' => 'Map',
+ 'Wagen' => 'Composition',
+
+ # wagon order
+ 'Nach' => 'To',
+ 'in Abschnitt' => 'in sections',
+ 'Wagen ' => 'carriage ',
+
+ # map
+ 'Fahrt' => 'Trip',
+ 'von' => 'from',
+ 'nach' => 'to',
+ 'Nächster Halt:' => 'Next stop:',
+ 'um' => 'at',
+ 'auf Gleis' => 'on platform',
+ 'Aufenthalt in' => 'Stopped in',
+ 'an Gleis' => 'on platform',
+ 'bis' => 'until',
+ 'Abfahrt in' => 'Departs',
+ 'von Gleis' => 'from platform',
+ 'Endstation erreicht um' => 'Terminus reached at',
+);
+
+1;