diff options
Diffstat (limited to 'lib/Travelynx.pm')
| -rwxr-xr-x | lib/Travelynx.pm | 964 |
1 files changed, 836 insertions, 128 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index c73f96d..39899c7 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -1,6 +1,7 @@ package Travelynx; # Copyright (C) 2020-2023 Birte Kristina Friesel +# Copyright (C) 2025 networkException <git@nwex.de> # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -19,10 +20,13 @@ use JSON; use List::Util; use List::UtilsBy qw(uniq_by); use List::MoreUtils qw(first_index); -use Travel::Status::DE::DBWagenreihung; -use Travelynx::Helper::DBDB; +use Travel::Status::DE::DBRIS::Formation; +use Travelynx::Helper::DBRIS; +use Travelynx::Helper::EFA; use Travelynx::Helper::HAFAS; use Travelynx::Helper::IRIS; +use Travelynx::Helper::Locales; +use Travelynx::Helper::MOTIS; use Travelynx::Helper::Sendmail; use Travelynx::Helper::Traewelling; use Travelynx::Model::InTransit; @@ -64,6 +68,8 @@ sub startup { $self->types->type( csv => 'text/csv; charset=utf-8' ); $self->types->type( json => 'application/json; charset=utf-8' ); + $self->types->type( gpx => 'application/gpx+xml; charset=utf-8' ); + $self->types->type( xml => 'text/xml; charset=utf-8' ); $self->plugin('Config'); @@ -153,15 +159,24 @@ sub startup { } ); + $self->hook( + 'before_render' => sub { + my ($self) = @_; + + $self->stash( loc_handle => $self->loc_handle ); + } + ); + $self->attr( cache_iris_main => sub { my ($self) = @_; - return Cache::File->new( + state $cache = Cache::File->new( cache_root => $self->app->config->{cache}->{schedule}, default_expires => '6 hours', lock_level => Cache::File::LOCK_LOCAL(), ); + return $cache; } ); @@ -169,11 +184,12 @@ sub startup { cache_iris_rt => sub { my ($self) = @_; - return Cache::File->new( + state $cache = Cache::File->new( cache_root => $self->app->config->{cache}->{realtime}, default_expires => '70 seconds', lock_level => Cache::File::LOCK_LOCAL(), ); + return $cache; } ); @@ -182,7 +198,7 @@ sub startup { $self->attr( ice_name => sub { state $id_to_name = { - Travel::Status::DE::DBWagenreihung::Group::name_to_designation( + Travel::Status::DE::DBRIS::Formation::Group::name_to_designation( ) }; return $id_to_name; @@ -191,7 +207,7 @@ sub startup { $self->attr( renamed_station => sub { - my $legacy_to_new = JSON->new->utf8->decode( + state $legacy_to_new = JSON->new->utf8->decode( scalar read_file('share/old_station_names.json') ); return $legacy_to_new; } @@ -217,10 +233,40 @@ sub startup { ); $self->helper( + efa => sub { + my ($self) = @_; + state $efa = Travelynx::Helper::EFA->new( + log => $self->app->log, + main_cache => $self->app->cache_iris_main, + realtime_cache => $self->app->cache_iris_rt, + root_url => $self->base_url_for('/')->to_abs, + user_agent => $self->ua, + version => $self->app->config->{version}, + ); + } + ); + + $self->helper( + dbris => sub { + my ($self) = @_; + state $dbris = Travelynx::Helper::DBRIS->new( + log => $self->app->log, + service_config => $self->app->config->{dbris}, + realtime_cache => $self->app->cache_iris_rt, + main_cache => $self->app->cache_iris_main, + root_url => $self->base_url_for('/')->to_abs, + user_agent => $self->ua, + version => $self->app->config->{version}, + ); + } + ); + + $self->helper( hafas => sub { my ($self) = @_; state $hafas = Travelynx::Helper::HAFAS->new( log => $self->app->log, + service_config => $self->app->config->{hafas}, main_cache => $self->app->cache_iris_main, realtime_cache => $self->app->cache_iris_rt, root_url => $self->base_url_for('/')->to_abs, @@ -244,6 +290,20 @@ sub startup { ); $self->helper( + motis => sub { + my ($self) = @_; + state $motis = Travelynx::Helper::MOTIS->new( + log => $self->app->log, + cache => $self->app->cache_iris_rt, + user_agent => $self->ua, + root_url => $self->base_url_for('/')->to_abs, + version => $self->app->config->{version}, + time_zone => 'Europe/Berlin', + ); + } + ); + + $self->helper( traewelling => sub { my ($self) = @_; state $trwl = Travelynx::Model::Traewelling->new( pg => $self->pg ); @@ -349,16 +409,59 @@ sub startup { ); $self->helper( - dbdb => sub { + loc_handle => sub { my ($self) = @_; - state $dbdb = Travelynx::Helper::DBDB->new( - log => $self->app->log, - main_cache => $self->app->cache_iris_main, - realtime_cache => $self->app->cache_iris_rt, - root_url => $self->base_url_for('/')->to_abs, - user_agent => $self->ua, - version => $self->app->config->{version}, - ); + + my @languages; + if ( $self->is_user_authenticated + and @{ $self->current_user->{languages} } ) + { + @languages = @{ $self->current_user->{languages} }; + } + elsif ( my $languages = $self->req->headers->accept_language ) { + for my $lang ( split( qr{ \s* , \s* }x, $languages ) ) { + if ( $lang =~ m{ ^ de }x ) { + push( @languages, 'de-DE' ); + } + elsif ( $lang =~ m{ ^ en }x ) { + push( @languages, 'en-GB' ); + } + elsif ( $lang =~ m{ ^ fr }x ) { + push( @languages, 'fr-FR' ); + } + elsif ( $lang =~ m{ ^ hu }x ) { + push( @languages, 'hu-HU' ); + } + elsif ( $lang =~ m{ ^ pl }x ) { + push( @languages, 'pl-PL' ); + } + } + } + + # en-GB and de-DE serve as fall-back languages, both in case + # we do not have the handle we need (here) and in case a string + # has only been translated to some languages (below). + + my $handle + = Travelynx::Helper::Locales->get_handle( @languages, 'en-GB', + 'de-DE' ); + my $first_fallback + = Travelynx::Helper::Locales->get_handle('en-GB'); + my $second_fallback + = Travelynx::Helper::Locales->get_handle('de-DE'); + + $handle->fail_with( + sub { $first_fallback->maketext( @_[ 1 .. $#_ ] ) } ); + $first_fallback->fail_with( + sub { $second_fallback->maketext( @_[ 1 .. $#_ ] ) } ); + return $handle; + } + ); + + $self->helper( + 'L' => sub { + my ( $self, @args ) = @_; + $self->stash('loc_handle')->maketext(@args); } ); @@ -399,11 +502,45 @@ sub startup { ); $self->helper( + 'efa_load_icon' => sub { + my ( $self, $occupancy ) = @_; + + my @symbols + = ( + qw(help_outline person_outline people priority_high not_interested) + ); + + 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; + } + + return $symbols[$occupancy] // 'help_outline'; + } + ); + + $self->helper( 'load_icon' => sub { my ( $self, $load ) = @_; my $first = $load->{FIRST} // 0; my $second = $load->{SECOND} // 0; + # DBRIS + if ( $first == 99 ) { + $first = 4; + } + if ( $second == 99 ) { + $second = 4; + } + my @symbols = ( qw(help_outline person_outline people priority_high not_interested) @@ -451,9 +588,18 @@ sub startup { return Mojo::Promise->reject('You are already checked in'); } + if ( $opt{dbris} ) { + return $self->_checkin_dbris_p(%opt); + } + if ( $opt{efa} ) { + return $self->_checkin_efa_p(%opt); + } if ( $opt{hafas} ) { return $self->_checkin_hafas_p(%opt); } + if ( $opt{motis} ) { + return $self->_checkin_motis_p(%opt); + } my $promise = Mojo::Promise->new; @@ -530,7 +676,7 @@ sub startup { ); $self->helper( - '_checkin_hafas_p' => sub { + '_checkin_motis_p' => sub { my ( $self, %opt ) = @_; my $station = $opt{station}; @@ -542,6 +688,430 @@ sub startup { my $promise = Mojo::Promise->new; + $self->motis->get_trip_p( + service => $opt{motis}, + trip_id => $train_id, + )->then( + sub { + my ($trip) = @_; + my $found_stopover; + + for my $stopover ( $trip->stopovers ) { + if ( $stopover->stop->id eq $station ) { + $found_stopover = $stopover; + + # Lines may serve the same stop several times. + # Keep looking until the scheduled departure + # matches the one passed while checking in. + if ( $ts + and $stopover->scheduled_departure->epoch + == $ts ) + { + last; + } + } + } + + if ( not $found_stopover ) { + $promise->reject( +"Did not find stopover at '$station' within trip '$train_id'" + ); + return; + } + + for my $stopover ( $trip->stopovers ) { + $self->stations->add_or_update( + stop => $stopover->stop, + db => $db, + motis => $opt{motis}, + ); + } + + $self->stations->add_or_update( + stop => $found_stopover->stop, + db => $db, + motis => $opt{motis}, + ); + + eval { + $self->in_transit->add( + uid => $uid, + db => $db, + journey => $trip, + stopover => $found_stopover, + data => { trip_id => $train_id }, + backend_id => $self->stations->get_backend_id( + motis => $opt{motis} + ), + ); + }; + + if ($@) { + $self->app->log->error( + "Checkin($uid): INSERT failed: $@"); + $promise->reject( 'INSERT failed: ' . $@ ); + return; + } + + my $polyline; + if ( $trip->polyline ) { + my @station_list; + my @coordinate_list; + for my $coordinate ( $trip->polyline ) { + if ( $coordinate->{stop} ) { + if ( not defined $coordinate->{stop}->{eva} ) { + die(); + } + + push( + @coordinate_list, + [ + $coordinate->{lon}, + $coordinate->{lat}, + $coordinate->{stop}->{eva} + ] + ); + + push( @station_list, + $coordinate->{stop}->name ); + } + else { + push( @coordinate_list, + [ $coordinate->{lon}, $coordinate->{lat} ] + ); + } + } + + # equal length → polyline only consists of straight + # lines between stops. that's not helpful. + if ( @station_list == @coordinate_list ) { + $self->log->debug( 'Ignoring polyline for ' + . $trip->route_name + . ' as it only consists of straight lines between stops.' + ); + } + else { + $polyline = { + from_eva => + ( $trip->stopovers )[0]->stop->{eva}, + to_eva => ( $trip->stopovers )[-1]->stop->{eva}, + coords => \@coordinate_list, + }; + } + } + + if ($polyline) { + $self->in_transit->set_polyline( + uid => $uid, + db => $db, + polyline => $polyline, + ); + } + + # mustn't be called during a transaction + if ( not $opt{in_transaction} ) { + $self->run_hook( $uid, 'checkin' ); + } + + $promise->resolve($trip); + } + )->catch( + sub { + my ($err) = @_; + $promise->reject($err); + return; + } + )->wait; + + return $promise; + } + ); + + $self->helper( + '_checkin_dbris_p' => sub { + my ( $self, %opt ) = @_; + + my $station = $opt{station}; + my $train_id = $opt{train_id}; + my $train_suffix = $opt{train_suffix}; + my $ts = $opt{ts}; + my $uid = $opt{uid} // $self->current_user->{id}; + my $db = $opt{db} // $self->pg->db; + my $hafas; + + my $promise = Mojo::Promise->new; + + $self->dbris->get_journey_p( + trip_id => $train_id, + with_polyline => 1 + )->then( + sub { + my ($journey) = @_; + my $found; + for my $stop ( $journey->route ) { + if ( $stop->eva eq $station ) { + $found = $stop; + + # Lines may serve the same stop several times. + # Keep looking until the scheduled departure + # matches the one passed while checking in. + if ( $ts and $stop->sched_dep->epoch == $ts ) { + last; + } + } + } + if ( not $found ) { + $promise->reject( +"Did not find stop '$station' within journey '$train_id'" + ); + return; + } + for my $stop ( $journey->route ) { + $self->stations->add_or_update( + stop => $stop, + db => $db, + dbris => 'bahn.de', + ); + } + eval { + $self->in_transit->add( + uid => $uid, + db => $db, + journey => $journey, + stop => $found, + data => { trip_id => $train_id }, + backend_id => $self->stations->get_backend_id( + dbris => 'bahn.de' + ), + train_suffix => $train_suffix, + ); + }; + if ($@) { + $self->app->log->error( + "Checkin($uid): INSERT failed: $@"); + $promise->reject( 'INSERT failed: ' . $@ ); + return; + } + + my $polyline; + if ( $journey->polyline ) { + my @station_list; + my @coordinate_list; + for my $coord ( $journey->polyline ) { + if ( $coord->{stop} ) { + push( + @coordinate_list, + [ + $coord->{lon}, $coord->{lat}, + $coord->{stop}->eva + ] + ); + push( @station_list, $coord->{stop}->name ); + } + else { + push( @coordinate_list, + [ $coord->{lon}, $coord->{lat} ] ); + } + } + + # equal length → polyline only consists of straight + # lines between stops. that's not helpful. + if ( @station_list == @coordinate_list ) { + $self->log->debug( 'Ignoring polyline for ' + . $journey->train + . ' as it only consists of straight lines between stops.' + ); + } + else { + $polyline = { + from_eva => ( $journey->route )[0]->eva, + to_eva => ( $journey->route )[-1]->eva, + coords => \@coordinate_list, + }; + } + } + + if ($polyline) { + $self->in_transit->set_polyline( + uid => $uid, + db => $db, + polyline => $polyline, + ); + } + + # mustn't be called during a transaction + if ( not $opt{in_transaction} ) { + $self->run_hook( $uid, 'checkin' ); + $self->add_wagonorder( + uid => $uid, + train_id => $train_id, + is_departure => 1, + eva => $found->eva, + datetime => $found->sched_dep, + train_type => $journey->type, + train_no => $journey->train_no, + ); + $self->add_stationinfo( $uid, 1, $train_id, + $found->eva ); + } + + $promise->resolve($journey); + } + )->catch( + sub { + my ($err) = @_; + $promise->reject($err); + return; + } + )->wait; + + return $promise; + } + ); + + $self->helper( + '_checkin_efa_p' => sub { + my ( $self, %opt ) = @_; + my $station = $opt{station}; + my $trip_id = $opt{train_id}; + my $ts = $opt{ts}; + my $uid = $opt{uid} // $self->current_user->{id}; + my $db = $opt{db} // $self->pg->db; + + my $promise = Mojo::Promise->new; + $self->efa->get_journey_p( + service => $opt{efa}, + trip_id => $trip_id + )->then( + sub { + my ($journey) = @_; + + my $found; + for my $stop ( $journey->route ) { + if ( $stop->id_num == $station ) { + $found = $stop; + + # Lines may serve the same stop several times. + # Keep looking until the scheduled departure + # matches the one passed while checking in. + if ( $ts and $stop->sched_dep->epoch == $ts ) { + last; + } + } + } + if ( not $found ) { + $promise->reject( +"Did not find stop '$station' within journey '$trip_id'" + ); + return; + } + + for my $stop ( $journey->route ) { + $self->stations->add_or_update( + stop => $stop, + db => $db, + efa => $opt{efa}, + ); + } + + eval { + $self->in_transit->add( + uid => $uid, + db => $db, + journey => $journey, + stop => $found, + trip_id => $trip_id, + backend_id => $self->stations->get_backend_id( + efa => $opt{efa} + ), + ); + }; + if ($@) { + $self->app->log->error( + "Checkin($uid): INSERT failed: $@"); + $promise->reject( 'INSERT failed: ' . $@ ); + return; + } + + my $polyline; + if ( $journey->polyline ) { + my @station_list; + my @coordinate_list; + for my $coord ( $journey->polyline ) { + if ( $coord->{stop} ) { + push( + @coordinate_list, + [ + $coord->{lon}, $coord->{lat}, + $coord->{stop}->id_num + ] + ); + push( @station_list, + $coord->{stop}->full_name ); + } + else { + push( @coordinate_list, + [ $coord->{lon}, $coord->{lat} ] ); + } + } + + # equal length → polyline only consists of straight + # lines between stops. that's not helpful. + if ( @station_list == @coordinate_list ) { + $self->log->debug( 'Ignoring polyline for ' + . $journey->line + . ' as it only consists of straight lines between stops.' + ); + } + else { + $polyline = { + from_eva => ( $journey->route )[0]->id_num, + to_eva => ( $journey->route )[-1]->id_num, + coords => \@coordinate_list, + }; + } + } + + if ($polyline) { + $self->in_transit->set_polyline( + uid => $uid, + db => $db, + polyline => $polyline, + ); + } + + # mustn't be called during a transaction + if ( not $opt{in_transaction} ) { + $self->run_hook( $uid, 'checkin' ); + } + + $promise->resolve($journey); + + return; + } + )->catch( + sub { + my ($err) = @_; + $promise->reject($err); + return; + } + )->wait; + return $promise; + } + ); + + $self->helper( + '_checkin_hafas_p' => sub { + my ( $self, %opt ) = @_; + + my $station = $opt{station}; + my $train_id = $opt{train_id}; + my $ts = $opt{ts}; + my $uid = $opt{uid} // $self->current_user->{id}; + my $db = $opt{db} // $self->pg->db; + + my $promise = Mojo::Promise->new; + $self->hafas->get_journey_p( service => $opt{hafas}, trip_id => $train_id, @@ -645,7 +1215,7 @@ sub startup { # mustn't be called during a transaction if ( not $opt{in_transaction} ) { $self->run_hook( $uid, 'checkin' ); - if ( $opt{hafas} eq 'DB' and $journey->class <= 16 ) { + if ( $opt{hafas} eq 'ÖBB' and $journey->class <= 16 ) { $self->add_wagonorder( uid => $uid, train_id => $journey->id, @@ -787,8 +1357,7 @@ sub startup { } if ( not $user->{checked_in} and not $user->{cancelled} ) { - return $promise->resolve( 0, - 'You are not checked into any train' ); + return $promise->resolve( 0, 'You are not checked in' ); } if ( $dep_eva and $dep_eva != $user->{dep_eva} ) { @@ -798,8 +1367,13 @@ sub startup { return $promise->resolve( 0, 'race condition' ); } - if ( $user->{is_hafas} ) { - return $self->_checkout_hafas_p(%opt); + if ( $user->{is_dbris} + or $user->{is_efa} + or $user->{is_hafas} + or $user->{is_motis} + or $train_id eq 'manual' ) + { + return $self->_checkout_journey_p(%opt); } my $now = DateTime->now( time_zone => 'Europe/Berlin' ); @@ -928,11 +1502,13 @@ sub startup { if ($has_arrived) { my @unknown_stations = $self->stations->grep_unknown( - $train->route ); + backend_id => $user->{backend_id}, + names => [ $train->route ] + ); if (@unknown_stations) { $self->app->log->warn( sprintf( -'Route of %s %s (%s -> %s) contains unknown stations: %s', +'IRIS: Route of %s %s (%s -> %s) contains unknown stations: %s', $train->type, $train->train_no, $train->origin, @@ -1048,7 +1624,7 @@ sub startup { ); $self->helper( - '_checkout_hafas_p' => sub { + '_checkout_journey_p' => sub { my ( $self, %opt ) = @_; my $station = $opt{station}; @@ -1074,7 +1650,7 @@ sub startup { my $has_arrived; for my $stop ( @{ $journey->{route_after} } ) { if ( $station eq $stop->[0] or $station eq $stop->[1] ) { - $found = 1; + $found = $stop; $self->in_transit->set_arrival_eva( uid => $uid, db => $db, @@ -1095,6 +1671,13 @@ sub startup { rt_arrival => ( $stop->[2]{rt_arr} || $stop->[2]{sched_arr} ) ); + if ( $stop->[2]{platform} ) { + $self->in_transit->set_arrival_platform( + uid => $uid, + db => $db, + arrival_platform => $stop->[2]{platform} + ); + } if ( $now > ( $stop->[2]{rt_arr} || $stop->[2]{sched_arr} ) ) { @@ -1142,6 +1725,22 @@ sub startup { uid => $uid ); } + elsif ( $found and $found->[2]{isCancelled} ) { + $journey = $self->in_transit->get( + uid => $uid, + db => $db + ); + $journey->{cancelled} = 1; + $self->journeys->add_from_in_transit( + db => $db, + journey => $journey + ); + $self->in_transit->set_cancelled_destination( + uid => $uid, + db => $db, + cancelled_destination => $found->[0], + ); + } if ($tx) { $tx->commit; @@ -1272,46 +1871,50 @@ sub startup { my $db = $self->pg->db; if ( $datetime and $train_no ) { - $self->dbdb->has_wagonorder_p(%opt)->then( + $self->dbris->get_wagonorder_p(%opt)->then( sub { - return $self->dbdb->get_wagonorder_p(%opt); - } - )->then( - sub { - my ($wagonorder) = @_; + my ($status) = @_; + my $wr = $status->result; my $data = {}; my $user_data = {}; - if ( $opt{is_departure} - and not exists $wagonorder->{error} ) + if ( $opt{is_departure} + and $wr ) { - $data->{wagonorder_dep} = $wagonorder; + my $dt + = $opt{datetime}->clone->set_time_zone('UTC'); + $data->{wagonorder_dep} = $status->{raw_json}; + $data->{wagonorder_param} = { + tt => $opt{train_type}, + tn => $opt{train_no}, + dt => $dt->epoch, + eva => $opt{eva} + }; $user_data->{wagongroups} = []; - for my $group ( @{ $wagonorder->{groups} // [] } ) { + for my $group ( $wr->groups ) { my @wagons; - for my $wagon ( @{ $group->{vehicles} // [] } ) - { + for my $wagon ( $group->carriages ) { push( @wagons, { - id => $wagon->{vehicleID}, - number => $wagon - ->{wagonIdentificationNumber}, - type => - $wagon->{type}{constructionType}, + id => $wagon->uic_id, + number => $wagon->number, + type => $wagon->type, } ); } push( @{ $user_data->{wagongroups} }, { - name => $group->{name}, - to => $group->{transport}{destination} - {name}, - type => $group->{transport}{category}, - no => $group->{transport}{number}, - wagons => [@wagons], + name => $group->name, + desc => $group->desc_short, + description => $group->description, + designation => $group->designation, + to => $group->destination, + type => $group->train_type, + no => $group->train_no, + wagons => [@wagons], } ); if ( $group->{name} @@ -1333,10 +1936,8 @@ sub startup { train_id => $train_id, ); } - elsif ( $opt{is_arrival} - and not exists $wagonorder->{error} ) - { - $data->{wagonorder_arr} = $wagonorder; + elsif ( $opt{is_arrival} ) { + $data->{wagonorder_arr} = $status->{raw_json}; $self->in_transit->update_data( uid => $uid, db => $db, @@ -1348,6 +1949,10 @@ sub startup { } )->catch( sub { + my ($err) = @_; + $self->log->debug( + "add_wagonorder: promise rejected with ${err}"); + # no wagonorder? no problem. return; } @@ -1423,6 +2028,7 @@ sub startup { $self->stations->add_or_update( stop => $stop, db => $db, + keep_name => 1, iris => 1, ); } @@ -1535,47 +2141,36 @@ sub startup { my $db = $self->pg->db; if ($is_departure) { - $self->dbdb->get_stationinfo_p($dep_eva)->then( - sub { - my ($station_info) = @_; - my $data = { stationinfo_dep => $station_info }; + if ( my $si + = $self->stations->get_bahn_stationinfo( eva => $dep_eva ) ) + { + my $data = { stationinfo_dep => $si }; - $self->in_transit->update_data( - uid => $uid, - db => $db, - data => $data, - train_id => $train_id, - ); - return; - } - )->catch( - sub { - # no stationinfo? no problem. - return; - } - )->wait; + $self->in_transit->update_data( + uid => $uid, + db => $db, + data => $data, + train_id => $train_id, + ); + return; + } } if ( $arr_eva and not $is_departure ) { - $self->dbdb->get_stationinfo_p($arr_eva)->then( - sub { - my ($station_info) = @_; - my $data = { stationinfo_arr => $station_info }; + if ( my $si + = $self->stations->get_bahn_stationinfo( eva => $arr_eva ) ) + { - $self->in_transit->update_data( - uid => $uid, - db => $db, - data => $data, - train_id => $train_id, - ); - return; - } - )->catch( - sub { - # no stationinfo? no problem. - return; - } - )->wait; + my $data = { stationinfo_arr => $si }; + + $self->in_transit->update_data( + uid => $uid, + db => $db, + data => $data, + train_id => $train_id, + ); + return; + } } } ); @@ -1590,7 +2185,17 @@ sub startup { $ret =~ s{[{]tt[}]}{$opt{tt}}g; $ret =~ s{[{]tn[}]}{$opt{tn}}g; $ret =~ s{[{]id[}]}{$opt{id}}g; + $ret =~ s{[{]dbris[}]}{$opt{dbris}}g; + $ret =~ s{[{]efa[}]}{$opt{efa}}g; $ret =~ s{[{]hafas[}]}{$opt{hafas}}g; + $ret =~ s{[{]motis[}]}{$opt{motis}}g; + + if ( $opt{id} and not $opt{is_iris} ) { + $ret =~ s{[{]id_or_tttn[}]}{$opt{id}}g; + } + else { + $ret =~ s{[{]id_or_tttn[}]}{$opt{tt}$opt{tn}}g; + } return $ret; } ); @@ -1621,8 +2226,8 @@ sub startup { my $wr; eval { $wr - = Travel::Status::DE::DBWagenreihung->new( - from_json => $wagonorder ); + = Travel::Status::DE::DBRIS::Formation->new( + json => $wagonorder ); }; if ( $wr and $wr->sectors @@ -1691,6 +2296,7 @@ sub startup { uid => $uid, db => $db, with_data => 1, + with_polyline => 1, with_timestamps => 1, with_visibility => 1, postprocess => 1, @@ -1767,8 +2373,8 @@ sub startup { my $wr; eval { $wr - = Travel::Status::DE::DBWagenreihung->new( - from_json => $in_transit->{data}{wagonorder_dep} ); + = Travel::Status::DE::DBRIS::Formation->new( + json => $in_transit->{data}{wagonorder_dep} ); }; if ( $wr and $wr->carriages @@ -1839,8 +2445,11 @@ sub startup { cancellation => $latest_cancellation, backend_id => $latest->{backend_id}, backend_name => $latest->{backend_name}, + is_dbris => $latest->{is_dbris}, + is_efa => $latest->{is_efa}, is_iris => $latest->{is_iris}, is_hafas => $latest->{is_hafas}, + is_motis => $latest->{is_motis}, journey_id => $latest->{journey_id}, timestamp => $action_time, timestamp_delta => $now->epoch - $action_time->epoch, @@ -1852,6 +2461,7 @@ sub startup { real_departure => epoch_to_dt( $latest->{real_dep_ts} ), dep_ds100 => $latest->{dep_ds100}, dep_eva => $latest->{dep_eva}, + dep_external_id => $latest->{dep_external_id}, dep_name => $latest->{dep_name}, dep_lat => $latest->{dep_lat}, dep_lon => $latest->{dep_lon}, @@ -1860,6 +2470,7 @@ sub startup { real_arrival => epoch_to_dt( $latest->{real_arr_ts} ), arr_ds100 => $latest->{arr_ds100}, arr_eva => $latest->{arr_eva}, + arr_external_id => $latest->{arr_external_id}, arr_name => $latest->{arr_name}, arr_lat => $latest->{arr_lat}, arr_lon => $latest->{arr_lon}, @@ -1900,8 +2511,12 @@ sub startup { ) ? \1 : \0, comment => $status->{comment}, backend => { - id => $status->{backend_id}, - type => $status->{is_hafas} ? 'HAFAS' : 'IRIS-TTS', + id => $status->{backend_id}, + type => $status->{is_dbris} ? 'DBRIS' + : $status->{is_efa} ? 'EFA' + : $status->{is_hafas} ? 'HAFAS' + : $status->{is_motis} ? 'MOTIS' + : 'IRIS-TTS', name => $status->{backend_name}, }, fromStation => { @@ -2024,8 +2639,7 @@ sub startup { my $db = $self->pg->db; my $tx = $db->begin; - $self->_checkin_hafas_p( - hafas => 'DB', + $self->_checkin_dbris_p( station => $traewelling->{dep_eva}, train_id => $traewelling->{trip_id}, uid => $uid, @@ -2034,8 +2648,7 @@ sub startup { )->then( sub { $self->log->debug("... handled origin"); - return $self->_checkout_hafas_p( - hafas => 'DB', + return $self->_checkout_journey_p( station => $traewelling->{arr_eva}, train_id => $traewelling->{trip_id}, uid => $uid, @@ -2120,22 +2733,54 @@ sub startup { my @stations = uniq_by { $_->{name} } map { { - name => $_->{to_name}, - latlon => $_->{to_latlon} + name => $_->{to_name} // $_->{arr_name}, + latlon => $_->{to_latlon} // $_->{arr_latlon}, }, { - name => $_->{from_name}, - latlon => $_->{from_latlon} + name => $_->{from_name} // $_->{dep_name}, + latlon => $_->{from_latlon} // $_->{dep_latlon} } } @journeys; + my @extra_stations; + + if ( $opt{show_all_stops} ) { + for my $journey (@journeys) { + my @j_stops = map { + { + name => $_->[2], + latlon => [ $_->[1], $_->[0] ] + } + } grep { defined $_->[2] } + @{ $journey->{polyline} // [] }; + @extra_stations + = uniq_by { $_->{name} } ( @extra_stations, @j_stops ); + } + } my @station_coordinates = map { [ $_->{latlon}, $_->{name} ] } @stations; + my @extra_station_coordinates + = map { [ $_->{latlon}, $_->{name} ] } @extra_stations; + + my @now_coordinates; + if ( $opt{with_now_markers} ) { + @now_coordinates = map { + [ + $_->{now_latlon}, + $_->{train_type} . ' ' + . ( $_->{train_line} // $_->{train_no} ) + ] + } @journeys; + } my @station_pairs; my @polylines; my %seen; + # not part of the travelled route, but trip route before/after the journey. + # Only used if show_full_route is set. + my @extra_polylines; + my @skipped_journeys; my @polyline_journeys = grep { $_->{polyline} } @journeys; my @beeline_journeys = grep { not $_->{polyline} } @journeys; @@ -2150,13 +2795,17 @@ sub startup { for my $journey (@polyline_journeys) { my @polyline = @{ $journey->{polyline} }; - my $from_eva = $journey->{from_eva}; - my $to_eva = $journey->{to_eva}; - - my $from_index - = first_index { $_->[2] and $_->[2] == $from_eva } @polyline; - my $to_index - = first_index { $_->[2] and $_->[2] == $to_eva } @polyline; + my $from_eva = $journey->{from_eva} // $journey->{dep_eva}; + my $to_eva = $journey->{to_eva} // $journey->{arr_eva}; + + # poly_dep_index, poly_arr_index are only available for + # journeys that were processed by get_travel_distance + # beforehand. However, they are much less error-prone than this + # first_index / last_index kludge when it comes to ring lines. + my $from_index = $journey->{poly_dep_index} + // first_index { $_->[2] and $_->[2] == $from_eva } @polyline; + my $to_index = $journey->{poly_arr_index} + // first_index { $_->[2] and $_->[2] == $to_eva } @polyline; # Work around inconsistencies caused by a multiple EVA IDs mapping to the same station name if ( $from_index == -1 ) { @@ -2187,7 +2836,7 @@ sub startup { or $to_index == -1 ) { # Fall back to route - delete $journey->{polyline}; + push( @beeline_journeys, $journey ); next; } @@ -2199,7 +2848,6 @@ sub startup { if ( $seen{$key} ) { next; } - $seen{$key} = 1; # direction does not matter at the moment @@ -2209,12 +2857,31 @@ sub startup { . ( $to_index - $from_index ); $seen{$key} = 1; + if ( $from_index > $to_index ) { + ( $to_index, $from_index ) = ( $from_index, $to_index ); + } + if ( $opt{show_full_route} ) { + my @pre_polyline = @polyline[ 0 .. $from_index ]; + my @post_polyline = @polyline[ $to_index .. $#polyline ]; + my @pre_polyline_coords; + for my $coord (@pre_polyline) { + push( @pre_polyline_coords, + [ $coord->[1], $coord->[0] ] ); + } + my @post_polyline_coords; + for my $coord (@post_polyline) { + push( @post_polyline_coords, + [ $coord->[1], $coord->[0] ] ); + } + push( @extra_polylines, + ( \@pre_polyline_coords, \@post_polyline_coords ) ); + } @polyline = @polyline[ $from_index .. $to_index ]; my @polyline_coords; for my $coord (@polyline) { push( @polyline_coords, [ $coord->[1], $coord->[0] ] ); } - push( @polylines, [@polyline_coords] ); + push( @polylines, \@polyline_coords ); } for my $journey (@beeline_journeys) { @@ -2222,13 +2889,19 @@ sub startup { my @route = @{ $journey->{route} }; my $from_index = first_index { - ( $_->[1] and $_->[1] == $journey->{from_eva} ) - or $_->[0] eq $journey->{from_name} + ( $_->[1] + and $_->[1] + == ( $journey->{from_eva} // $journey->{dep_eva} ) ) + or $_->[0] eq + ( $journey->{from_name} // $journey->{dep_name} ) } @route; my $to_index = first_index { - ( $_->[1] and $_->[1] == $journey->{to_eva} ) - or $_->[0] eq $journey->{to_name} + ( $_->[1] + and $_->[1] + == ( $journey->{to_eva} // $journey->{arr_eva} ) ) + or $_->[0] eq + ( $journey->{to_name} // $journey->{arr_name} ) } @route; @@ -2236,15 +2909,15 @@ sub startup { my $rename = $self->app->renamed_station; $from_index = first_index { ( $rename->{ $_->[0] } // $_->[0] ) eq - $journey->{from_name} + ( $journey->{from_name} // $journey->{dep_name} ) } @route; } if ( $to_index == -1 ) { my $rename = $self->app->renamed_station; $to_index = first_index { - ( $rename->{ $_->[0] } // $_->[0] ) eq - $journey->{to_name} + ( $rename->{ $_->[0] } // $_->[0] ) eq + ( $journey->{to_name} // $journey->{arr_name} ) } @route; } @@ -2267,7 +2940,8 @@ sub startup { # and entered manually (-> beeline also shown on map, typically # significantly differs from detailed route) -- unless the user # sets include_manual, of course. - if ( $journey->{edited} & 0x0010 + if ( $journey->{edited} + and $journey->{edited} & 0x0010 and @route <= 2 and not $include_manual ) { @@ -2315,7 +2989,7 @@ sub startup { { polylines => $json->encode( \@station_pairs ), color => '#673ab7', - opacity => @polylines + opacity => scalar @polylines ? $with_polyline ? 0.4 : 0.6 @@ -2325,8 +2999,14 @@ sub startup { polylines => $json->encode( \@polylines ), color => '#673ab7', opacity => 0.8, - } + }, + { + polylines => $json->encode( \@extra_polylines ), + color => '#665577', + opacity => 0.6, + }, ], + markers => \@now_coordinates, }; if (@station_coordinates) { @@ -2340,6 +3020,13 @@ sub startup { = [ [ $min_lat, $min_lon ], [ $max_lat, $max_lon ] ]; } + if (@extra_station_coordinates) { + $ret->{station_coordinates} = [ + uniq_by { $_->[1] } + ( @station_coordinates, @extra_station_coordinates ) + ]; + } + return $ret; } ); @@ -2407,10 +3094,17 @@ sub startup { if ( $self->is_user_authenticated ) { return 1; } - $self->render( - 'login', - redirect_to => $self->req->url, - from => 'auth_required' + $self->respond_to( + json => { + json => { error => 'authentication required' }, + status => 401 + }, + any => { + template => 'login', + status => 401, + redirect_to => $self->req->url, + from => 'auth_required' + } ); return undef; } @@ -2424,9 +3118,11 @@ sub startup { $authed_r->get('/account/hooks')->to('account#webhook'); $authed_r->get('/account/traewelling')->to('traewelling#settings'); $authed_r->get('/account/insight')->to('account#insight'); + $authed_r->get('/account/language')->to('account#change_language'); $authed_r->get('/ajax/status_card.html')->to('traveling#status_card'); $authed_r->get( '/cancelled' => [ format => [ 'html', 'json' ] ] ) ->to( 'traveling#cancelled', format => undef ); + $authed_r->get('/checkin/add')->to('traveling#add_intransit_form'); $authed_r->get('/fgr')->to('passengerrights#list_candidates'); $authed_r->get('/account/password')->to('account#password_form'); $authed_r->get('/account/mail')->to('account#change_mail'); @@ -2441,10 +3137,19 @@ sub startup { $authed_r->get('/history/:year')->to('traveling#yearly_history'); $authed_r->get('/history/:year/review')->to('traveling#year_in_review'); $authed_r->get('/history/:year/:month')->to('traveling#monthly_history'); - $authed_r->get('/journey/add')->to('traveling#add_journey_form'); + $authed_r->get('/journey/add') + ->to('traveling#add_journey_form') + ->name('add_journey'); $authed_r->get('/journey/comment')->to('traveling#comment_form'); $authed_r->get('/journey/visibility')->to('traveling#visibility_form'); - $authed_r->get('/journey/:id')->to('traveling#journey_details'); + $authed_r->get( '/journey/:id' => [ format => [ 'html', 'json' ] ] ) + ->to( 'traveling#journey_details', format => undef ) + ->name('journey'); + $authed_r->get( '/polyline/:id' => [ format => [ 'gpx', 'json' ] ] )->to( + 'traveling#journey_details', + format => undef, + polyline_export => 1 + )->name('polyline_download'); $authed_r->get('/s/*station')->to('traveling#station'); $authed_r->get('/confirm_mail/:token')->to('account#confirm_mail'); $authed_r->post('/account/privacy')->to('account#privacy'); @@ -2453,8 +3158,11 @@ sub startup { $authed_r->post('/account/hooks')->to('account#webhook'); $authed_r->post('/account/traewelling')->to('traewelling#settings'); $authed_r->post('/account/insight')->to('account#insight'); + $authed_r->post('/account/language')->to('account#change_language'); $authed_r->post('/account/select_backend')->to('account#change_backend'); + $authed_r->post('/checkin/add')->to('traveling#add_intransit_form'); $authed_r->post('/journey/add')->to('traveling#add_journey_form'); + $authed_r->post('/polyline/set')->to('traveling#set_polyline'); $authed_r->post('/journey/comment')->to('traveling#comment_form'); $authed_r->post('/journey/visibility')->to('traveling#visibility_form'); $authed_r->post('/journey/edit')->to('traveling#edit_journey'); |
