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') ?? ''); | 
