diff options
author | networkException <git@nwex.de> | 2025-04-18 12:27:41 +0200 |
---|---|---|
committer | Birte Kristina Friesel <derf@finalrewind.org> | 2025-04-21 13:01:05 +0200 |
commit | 6dab985966f2a67a9acfdc5b442b16d5e65b7079 (patch) | |
tree | 049e752d3cfdfbc4dbfa41494f86922502a68a9d | |
parent | 75411af57219a256a767361cb7c8caae1477ba9c (diff) |
Initial MOTIS backend support
This patch adds support for displaying coverage and trip
maps for MOTIS.
-rw-r--r-- | lib/DBInfoscreen.pm | 15 | ||||
-rw-r--r-- | lib/DBInfoscreen/Controller/Map.pm | 260 | ||||
-rw-r--r-- | lib/DBInfoscreen/Helper/MOTIS.pm | 82 | ||||
-rw-r--r-- | public/static/js/map-refresh.js | 1 |
4 files changed, 358 insertions, 0 deletions
diff --git a/lib/DBInfoscreen.pm b/lib/DBInfoscreen.pm index b0bac14..18a2c87 100644 --- a/lib/DBInfoscreen.pm +++ b/lib/DBInfoscreen.pm @@ -10,6 +10,7 @@ use Cache::File; use DBInfoscreen::Helper::DBRIS; use DBInfoscreen::Helper::EFA; use DBInfoscreen::Helper::HAFAS; +use DBInfoscreen::Helper::MOTIS; use DBInfoscreen::Helper::Wagonorder; use File::Slurp qw(read_file); use JSON; @@ -107,6 +108,20 @@ sub startup { ); $self->helper( + motis => sub { + my ($self) = @_; + state $motis = DBInfoscreen::Helper::MOTIS->new( + log => $self->app->log, + main_cache => $self->app->cache_iris_main, + realtime_cache => $self->app->cache_iris_rt, + root_url => $self->url_for('/')->to_abs, + user_agent => $self->ua, + version => $self->config->{version}, + ); + } + ); + + $self->helper( efa => sub { my ($self) = @_; state $efa = DBInfoscreen::Helper::EFA->new( diff --git a/lib/DBInfoscreen/Controller/Map.pm b/lib/DBInfoscreen/Controller/Map.pm index 405b2e5..cfe7259 100644 --- a/lib/DBInfoscreen/Controller/Map.pm +++ b/lib/DBInfoscreen/Controller/Map.pm @@ -1,6 +1,7 @@ package DBInfoscreen::Controller::Map; # Copyright (C) 2011-2020 Birte Kristina Friesel +# Copyright (C) 2025 networkException <git@nwex.de> # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -629,6 +630,175 @@ sub route_dbris { )->wait; } +sub route_motis { + my ($self) = @_; + + my $service = $self->param('motis') // 'transitous'; + my $trip_id = $self->stash('tripid'); + + my $from_name = $self->param('from'); + my $to_name = $self->param('to'); + + $self->motis->get_polyline_p( + service => $service, + id => $trip_id, + )->then( + sub { + my ($trip) = @_; + + my @polyline = $trip->polyline; + my @station_coordinates; + + my @markers; + + my $now = DateTime->now( time_zone => 'Europe/Berlin' ); + + # used to draw the train's journey on the map + my @line_pairs = polyline_to_line_pairs(@polyline); + + my @stopovers = $trip->stopovers; + + my $train_pos = $self->estimate_train_positions2( + now => $now, + route => [ + map { + { + name => $_->stop->name, + arr => $_->arrival, + dep => $_->departure, + arr_delay => $_->arrival_delay, + dep_delay => $_->departure_delay, + lat => $_->stop->lat, + lon => $_->stop->lon, + } + } @stopovers + ], + polyline => \@polyline, + ); + + # Prepare from/to markers and name/time/delay overlays for stations + for my $stopover (@stopovers) { + my $stop = $stopover->stop; + my @stop_lines = ( $stop->name ); + + if ( $from_name and $stop->name eq $from_name ) { + push( + @markers, + { + lon => $stop->lon, + lat => $stop->lat, + title => $stop->name, + icon => 'goldIcon', + } + ); + } + if ( $to_name and $stop->name eq $to_name ) { + push( + @markers, + { + lon => $stop->lon, + lat => $stop->lat, + title => $stop->name, + icon => 'greenIcon', + } + ); + } + + if ( $stopover->track ) { + push( @stop_lines, 'Gleis ' . $stop->track ); + } + if ( $stopover->arrival ) { + my $arr_line = $stopover->arrival->strftime('Ankunft: %H:%M'); + if ( $stopover->arrival_delay ) { + $arr_line .= sprintf( ' (%+d)', $stopover->arrival_delay ); + } + push( @stop_lines, $arr_line ); + } + if ( $stopover->departure ) { + my $dep_line = $stopover->departure->strftime('Abfahrt: %H:%M'); + if ( $stopover->departure_delay ) { + $dep_line .= sprintf( ' (%+d)', $stopover->departure_delay ); + } + push( @stop_lines, $dep_line ); + } + + push( @station_coordinates, + [ [ $stop->lat, $stop->lon ], [@stop_lines], ] ); + } + + push( + @markers, + { + lat => $train_pos->{position_now}[0], + lon => $train_pos->{position_now}[1], + title => $trip->route_name, + } + ); + + $self->render( + 'route_map', + description => "Karte für " . $trip->route_name, + title => $trip->route_name, + hide_opts => 1, + with_map => 1, + ajax_req => "${trip_id}/0", + ajax_route => route_to_ajax( + map { + { + name => $_->stop->name, + platform => $_->track, + arr => $_->arrival, + arr_cancelled => $_->is_cancelled, + arr_delay => $_->arrival_delay, + dep => $_->departure, + dep_cancelled => $_->is_cancelled, + dep_delay => $_->departure_delay, + } + } $trip->stopovers + ), + ajax_polyline => join( + '|', + map { join( ';', @{$_} ) } @{ $train_pos->{positions} } + ), + origin => { + name => ( $trip->stopovers )[0]->stop->name, + ts => ( $trip->stopovers )[0]->departure, + }, + destination => { + name => ( $trip->stopovers )[-1]->stop->name, + ts => ( $trip->stopovers )[-1]->arrival, + }, + train_no => undef, # FIXME: Better value? + next_stop => $train_pos->{next_stop}, + polyline_groups => [ + { + polylines => [@line_pairs], + color => '#00838f', + opacity => 0.6, + fit_bounds => 1, + } + ], + station_coordinates => [@station_coordinates], + station_radius => + ( $train_pos->{avg_inter_stop_beeline} > 500 ? 250 : 100 ), + markers => [@markers], + ); + } + )->catch( + sub { + my ($err) = @_; + $self->render( + 'route_map', + title => "DBF", + hide_opts => 1, + with_map => 1, + error => $err, + ); + + } + )->wait; +} + sub route { my ($self) = @_; my $trip_id = $self->stash('tripid'); @@ -643,6 +813,9 @@ sub route { if ( $self->param('dbris') ) { return $self->route_dbris; } + if ( $self->param('motis') ) { + return $self->route_motis; + } if ( $self->param('efa') ) { return $self->route_efa; } @@ -992,6 +1165,87 @@ sub ajax_route_dbris { )->wait; } +sub ajax_route_motis { + my ($self) = @_; + + my $service = $self->param('motis') // 'transitous'; + my $trip_id = $self->stash('tripid'); + + $self->motis->get_polyline_p( + service => $service, + id => $trip_id, + )->then( + sub { + my ($trip) = @_; + + my $now = DateTime->now( time_zone => 'Europe/Berlin' ); + + my @stopovers = $trip->stopovers; + my @polyline = $trip->polyline; + + my $train_pos = $self->estimate_train_positions2( + now => $now, + route => [ + map { + { + name => $_->stop->name, + arr => $_->arrival, + dep => $_->departure, + arr_delay => $_->arrival_delay, + dep_delay => $_->departure_delay, + lat => $_->stop->lat, + lon => $_->stop->lon, + } + } @stopovers + ], + polyline => \@polyline, + ); + + $self->render( + '_map_infobox', + ajax_req => "${trip_id}/0", + ajax_route => route_to_ajax( + map { + { + name => $_->stop->name, + platform => $_->track, + arr => $_->arrival, + arr_cancelled => $_->is_cancelled, + arr_delay => $_->arrival_delay, + dep => $_->departure, + dep_cancelled => $_->is_cancelled, + dep_delay => $_->departure_delay, + } + } @stopovers + ), + ajax_polyline => join( + '|', + map { join( ';', @{$_} ) } @{ $train_pos->{positions} } + ), + origin => { + name => ( $trip->stopovers )[0]->stop->name, + ts => ( $trip->stopovers )[0]->departure, + }, + destination => { + name => ( $trip->stopovers )[-1]->stop->name, + ts => ( $trip->stopovers )[-1]->arrival, + }, + train_no => undef, # FIXME + next_stop => $train_pos->{next_stop}, + platform_type => q{}, + ); + } + )->catch( + sub { + my ($err) = @_; + $self->render( + '_error', + error => $err, + ); + } + )->wait; +} + sub ajax_route { my ($self) = @_; @@ -1002,6 +1256,9 @@ sub ajax_route { if ( $self->param('dbris') ) { return $self->ajax_route_dbris; } + if ( $self->param('motis') ) { + return $self->ajax_route_motis; + } if ( $self->param('efa') ) { return $self->ajax_route_efa; } @@ -1108,6 +1365,9 @@ sub coverage { elsif ( $backend eq 'hafas' ) { $coverage = $self->hafas->get_coverage($service); } + elsif ( $backend eq 'motis' ) { + $coverage = $self->motis->get_coverage($service); + } $self->render( 'coverage_map', diff --git a/lib/DBInfoscreen/Helper/MOTIS.pm b/lib/DBInfoscreen/Helper/MOTIS.pm new file mode 100644 index 0000000..002a601 --- /dev/null +++ b/lib/DBInfoscreen/Helper/MOTIS.pm @@ -0,0 +1,82 @@ +package DBInfoscreen::Helper::MOTIS; + +# Copyright (C) 2025 networkException <git@nwex.de> +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +use strict; +use warnings; +use 5.020; + +use DateTime; +use Encode qw(decode encode); +use Travel::Status::MOTIS; +use Mojo::JSON qw(decode_json); +use Mojo::Promise; + +sub new { + my ( $class, %opt ) = @_; + + my $version = $opt{version}; + + $opt{header} + = { 'User-Agent' => +"dbf/${version} on $opt{root_url} +https://finalrewind.org/projects/db-fakedisplay" + }; + + return bless( \%opt, $class ); + +} + +sub get_coverage { + my ( $self, $service ) = @_; + + my $service_definition = Travel::Status::MOTIS::get_service($service); + + if ( not $service_definition ) { + return {}; + } + + return $service_definition->{coverage}{area} // {}; +} + +# Input: TripID +# Output: Promise returning a Travel::Status::MOTIS::Trip instance on success +sub get_polyline_p { + my ( $self, %opt ) = @_; + + my $trip_id = $opt{id}; + my $service = $opt{service} // 'transitous'; + + my $promise = Mojo::Promise->new; + + my $agent = $self->{user_agent}; + + Travel::Status::MOTIS->new_p( + cache => $self->{realtime_cache}, + promise => 'Mojo::Promise', + user_agent => $agent->request_timeout(10), + + service => $service, + trip_id => $trip_id, + )->then( + sub { + my ($motis) = @_; + my $trip = $motis->result; + + $promise->resolve($trip); + return; + } + )->catch( + sub { + my ($err) = @_; + $self->{log}->debug("MOTIS->new_p($trip_id) error: $err"); + $promise->reject($err); + return; + } + )->wait; + + return $promise; +} + +1; diff --git a/public/static/js/map-refresh.js b/public/static/js/map-refresh.js index ec7c321..fcaac86 100644 --- a/public/static/js/map-refresh.js +++ b/public/static/js/map-refresh.js @@ -72,6 +72,7 @@ function dbf_map_reload() { const new_params = new URLSearchParams(); new_params.set('dbris', param.get('dbris') ?? ''); + new_params.set('motis', param.get('motis') ?? ''); new_params.set('efa', param.get('efa') ?? ''); new_params.set('hafas', param.get('hafas') ?? ''); |