package DBInfoscreen::Controller::Map;

# Copyright (C) 2011-2020 Birte Kristina Friesel
#
# SPDX-License-Identifier: AGPL-3.0-or-later

use Mojo::Base 'Mojolicious::Controller';
use Mojo::JSON qw(decode_json encode_json);
use Mojo::Promise;

use DateTime;
use DateTime::Format::Strptime;
use GIS::Distance;
use List::Util qw();
use Travel::Status::DE::EFA;

my $strp = DateTime::Format::Strptime->new(
	pattern   => '%Y-%m-%dT%H:%M:%S%z',
	time_zone => 'Europe/Berlin',
);

# Input:
#   - polyline: [{lat, lon, name?}, ...]
#   - from_name: station name
#   - to_name: station name
# Ouptut:
#   - from_index: polyline index where name eq from_name
#   - to_index: polyline index where name eq to_name
sub get_route_indexes {
	my ( $polyline, $from_name, $to_name ) = @_;
	my ( $from_index, $to_index );

	for my $i ( 0 .. $#{$polyline} ) {
		my $this_point = $polyline->[$i];
		if (    not defined $from_index
			and $this_point->{name}
			and $this_point->{name} eq $from_name )
		{
			$from_index = $i;
		}
		elsif ( $this_point->{name}
			and $this_point->{name} eq $to_name )
		{
			$to_index = $i;
			last;
		}
	}
	return ( $from_index, $to_index );
}

# Input:
#   now: DateTime
#   from: current/previous stop
#         {arr => DateTime, dep => DateTime, name => str, lat => float, lon => float}
#   to: next stop
#       {arr => DateTime, dep => DateTime, name => str, lat => float, lon => float}
#   route: Travel::Status::DE::HAFAS::Journey->route
#   polyline: Travel::Status::DE::HAFAS::Journey->polyline (list of lon/lat hashes)
# Output: list of estimated train positions in [lat, lon] format.
# - current position
# - position 2 seconds from now
# - position 4 seconds from now
# - ...
sub estimate_train_positions {
	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 $route     = $opt{route};
	my $polyline  = $opt{polyline};

	my @train_positions;

	my $time_complete = $now->epoch - $from_dt->epoch;
	my $time_total    = $to_dt->epoch - $from_dt->epoch;

	my @completion_ratios
	  = map { ( $time_complete + ( $_ * 2 ) ) / $time_total } ( 0 .. 45 );

	my $distance = GIS::Distance->new;

	my ( $from_index, $to_index )
	  = get_route_indexes( $polyline, $from_name, $to_name );

	if ( defined $from_index and defined $to_index ) {
		my $total_distance = 0;
		for my $j ( $from_index + 1 .. $to_index ) {
			my $prev = $polyline->[ $j - 1 ];
			my $this = $polyline->[$j];
			if ( $prev and $this ) {
				$total_distance
				  += $distance->distance_metal( $prev->{lat}, $prev->{lon},
					$this->{lat}, $this->{lon} );
			}
		}
		my @marker_distances = map { $total_distance * $_ } @completion_ratios;
		$total_distance = 0;
		for my $j ( $from_index + 1 .. $to_index ) {
			my $prev = $polyline->[ $j - 1 ];
			my $this = $polyline->[$j];
			if ( $prev and $this ) {
				my $prev_distance = $total_distance;
				$total_distance
				  += $distance->distance_metal( $prev->{lat}, $prev->{lon},
					$this->{lat}, $this->{lon} );
				for my $i ( @train_positions .. $#marker_distances ) {
					my $marker_distance = $marker_distances[$i];
					if ( $total_distance > $marker_distance ) {

						# completion ratio for the line between (prev, this)
						my $sub_ratio = 1;
						if ( $total_distance != $prev_distance ) {
							$sub_ratio = ( $marker_distance - $prev_distance )
							  / ( $total_distance - $prev_distance );
						}

						my $lat = $prev->{lat}
						  + ( $this->{lat} - $prev->{lat} ) * $sub_ratio;
						my $lon = $prev->{lon}
						  + ( $this->{lon} - $prev->{lon} ) * $sub_ratio;

						push( @train_positions, [ $lat, $lon ] );
					}
				}
				if ( @train_positions == @completion_ratios ) {
					return @train_positions;
				}
			}
		}
		if (@train_positions) {
			return @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;
			my $lon
			  = $opt{from}{lon} + ( $opt{to}{lon} - $opt{from}{lon} ) * $ratio;
			push( @train_positions, [ $lat, $lon ] );
		}
		return @train_positions;
	}
	return [ $opt{to}{lat}, $opt{to}{lon} ];
}

# Input:
#   now: DateTime
#   route: arrayref of hashrefs
#     lat: float
#     lon: float
#     name: str
#     arr: DateTime
#     dep: DateTime
#     arr_delay: int
#     dep_delay: int
#   polyline: ref to Travel::Status::DE::HAFAS::Journey polyline list
#  Output:
#    next_stop: {type, station}
#    positions: [current position [lat, lon], 2s from now, 4s from now, ...]
sub estimate_train_positions2 {
	my ( $self, %opt ) = @_;
	my $now   = $opt{now};
	my @route = @{ $opt{route} // [] };

	my @train_positions;
	my $next_stop;
	my $distance               = GIS::Distance->new;
	my $stop_distance_sum      = 0;
	my $avg_inter_stop_beeline = 0;

	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} ) )
		{

			# 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 = $self->estimate_train_positions(
				from     => $route[ $i - 1 ],
				to       => $route[$i],
				now      => $now,
				route    => $opt{route},
				polyline => $opt{polyline},
			);

			$next_stop = {
				type    => 'next',
				station => $route[$i],
			};
		}
		if (    not $next_stop
			and ( $route[ $i - 1 ]{dep} // $route[ $i - 1 ]{arr} )
			and $now <= ( $route[ $i - 1 ]{dep} // $route[ $i - 1 ]{arr} ) )
		{
			@train_positions
			  = ( [ $route[ $i - 1 ]{lat}, $route[ $i - 1 ]{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}
		) / 1000;
	}

	if ($#route) {
		$avg_inter_stop_beeline = $stop_distance_sum / $#route;
	}

	if ( @route and not $next_stop ) {
		@train_positions = ( [ $route[-1]{lat}, $route[-1]{lon} ] );
		$next_stop       = {
			type    => 'present',
			station => $route[-1]
		};
	}

	my $position_now = shift @train_positions;

	return {
		next_stop              => $next_stop,
		avg_inter_stop_beeline => $avg_inter_stop_beeline,
		position_now           => $position_now,
		positions              => \@train_positions,
	};
}

# input: [{
#   name, platform,
#   arr, arr_cancelled, arr_delay,
#   dep, dep_cancelled, dep_delay
# }]
sub route_to_ajax {
	my (@stopovers) = @_;

	my @route_entries;

	for my $stop (@stopovers) {
		my @stop_entries = ( $stop->{name} );
		my $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 );
		}
		else {
			push( @stop_entries, q{}, 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 );
		}
		else {
			push( @stop_entries, q{}, q{}, q{} );
		}

		push( @route_entries, join( ';', @stop_entries ) );
	}

	return join( '|', @route_entries );
}

sub polyline_to_line_pairs {
	my (@polyline) = @_;
	my @line_pairs;
	for my $i ( 1 .. $#polyline ) {
		push(
			@line_pairs,
			[
				[ $polyline[ $i - 1 ]{lat}, $polyline[ $i - 1 ]{lon} ],
				[ $polyline[$i]{lat},       $polyline[$i]{lon} ]
			]
		);
	}
	return @line_pairs;
}

sub backpropagate_delay {
	my ( $self, $prev_stop, $next_stop ) = @_;

	if ( ( $next_stop->{arr_delay} || $next_stop->{dep_delay} )
		and not( $prev_stop->{dep_delay} || $prev_stop->{arr_delay} ) )
	{
		$self->log->debug("need to back-propagate delay");
		my $delay = $next_stop->{arr_delay} || $next_stop->{dep_delay};
		if ( $prev_stop->{arr} ) {
			$prev_stop->{arr}->add( minutes => $delay );
			$prev_stop->{arr_delay} = $delay;
		}
		if ( $prev_stop->{dep} ) {
			$prev_stop->{dep}->add( minutes => $delay );
			$prev_stop->{dep_delay} = $delay;
		}
	}
}

sub estimate_polyline_stops {
	my ( $self, $polyline, $route ) = @_;

	my $distance = GIS::Distance->new;

	my %min_dist;
	for my $stop ( @{$route} ) {
		for my $polyline_index ( 0 .. $#{$polyline} ) {
			my $pl   = $polyline->[$polyline_index];
			my $dist = $distance->distance_metal( $stop->{lat}, $stop->{lon},
				$pl->{lat}, $pl->{lon} );
			if ( not $min_dist{ $stop->{name} }
				or $min_dist{ $stop->{name} }{dist} > $dist )
			{
				$min_dist{ $stop->{name} } = {
					dist => $distance->distance_metal(
						$stop->{lat}, $stop->{lon}, $pl->{lat}, $pl->{lon}
					),
					index => $polyline_index,
				};
			}
		}
	}

	for my $stop ( @{$route} ) {
		if ( $min_dist{ $stop->{name} } ) {
			$polyline->[ $min_dist{ $stop->{name} }{index} ]{name}
			  = $stop->{name};
		}
	}
}

sub route_efa {
	my ($self)  = @_;
	my $trip_id = $self->stash('tripid');
	my $backend = $self->param('efa');

	my $stopseq;
	if ( $trip_id =~ m{ ^ ([^@]*) @ ([^@]*) [(] ([^)]*) [)] (.*)  $ }x ) {
		$stopseq = {
			stateless => $1,
			stop_id   => $2,
			date      => $3,
			key       => $4
		};
	}
	else {
		$self->render(
			'route_map',
			title     => "DBF",
			hide_opts => 1,
			with_map  => 1,
			error     => "cannot parse trip ID: $trip_id",
		);
		return;
	}

	$self->efa->get_polyline_p(
		stopseq => $stopseq,
		service => $backend,
	)->then(
		sub {
			my ($trip) = @_;
			my $now = DateTime->now( time_zone => 'Europe/Berlin' );
			my @markers;
			my @polyline
			  = map { { lat => $_->[0], lon => $_->[1] } }
			  $trip->polyline( fallback => 1 );
			my @line_pairs = polyline_to_line_pairs(@polyline);
			my @route      = $trip->route;

			my $ref_route = [
				map {
					{
						name      => $_->full_name,
						platform  => $_->platform,
						arr       => $_->arr,
						dep       => $_->dep,
						arr_delay => $_->arr_delay,
						dep_delay => $_->dep_delay,
						lat       => $_->latlon->[0],
						lon       => $_->latlon->[1]
					}
				} @route
			];
			$self->estimate_polyline_stops( \@polyline, $ref_route );

			my $train_pos = $self->estimate_train_positions2(
				now      => $now,
				route    => $ref_route,
				polyline => \@polyline,
			);

			my @station_coordinates;
			for my $stop (@route) {
				my @stop_lines = ( $stop->full_name );
				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 );
					}
					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 );
					}
					push( @stop_lines, $dep_line );
				}

				push( @station_coordinates, [ $stop->latlon, [@stop_lines], ] );
			}

			push(
				@markers,
				{
					lat   => $train_pos->{position_now}[0],
					lon   => $train_pos->{position_now}[1],
					title => $trip->name,
				}
			);

			$self->render(
				'route_map',
				description   => "Karte für " . $trip->name,
				title         => $trip->name,
				hide_opts     => 1,
				with_map      => 1,
				ajax_req      => "${trip_id}/0",
				ajax_route    => route_to_ajax( @{$ref_route} ),
				ajax_polyline => join( '|',
					map { join( ';', @{$_} ) } @{ $train_pos->{positions} } ),
				,
				origin => {
					name => ( $trip->route )[0]->full_name,
					ts   => ( $trip->route )[0]->dep,
				},
				destination => {
					name => ( $trip->route )[-1]->full_name,
					ts   => ( $trip->route )[-1]->arr,
				},
				train_no => $trip->number
				? ( $trip->type // q{} . ' ' . $trip->number )
				: undef,
				operator        => $trip->operator,
				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      => 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');
	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;

	if ( $self->param('efa') ) {
		return $self->route_efa;
	}

	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) = @_;

			my @polyline = $journey->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 @route = $journey->route;

			my $train_pos = $self->estimate_train_positions2(
				now   => $now,
				route => [
					map {
						{
							name      => $_->loc->name,
							arr       => $_->arr,
							dep       => $_->dep,
							arr_delay => $_->arr_delay,
							dep_delay => $_->dep_delay,
							lat       => $_->loc->lat,
							lon       => $_->loc->lon
						}
					} @route
				],
				polyline => \@polyline,
			);

			# Prepare from/to markers and name/time/delay overlays for stations
			for my $stop (@route) {
				my @stop_lines = ( $stop->loc->name );

				if ( $from_name and $stop->loc->name eq $from_name ) {
					push(
						@markers,
						{
							lon   => $stop->loc->lon,
							lat   => $stop->loc->lat,
							title => $stop->loc->name,
							icon  => 'goldIcon',
						}
					);
				}
				if ( $to_name and $stop->loc->name eq $to_name ) {
					push(
						@markers,
						{
							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->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 );
					}
					push( @stop_lines, $dep_line );
				}

				push( @station_coordinates,
					[ [ $stop->loc->lat, $stop->loc->lon ], [@stop_lines], ] );
			}

			push(
				@markers,
				{
					lat   => $train_pos->{position_now}[0],
					lon   => $train_pos->{position_now}[1],
					title => $journey->name
				}
			);

			$self->render(
				'route_map',
				description => "Karte für " . $journey->name,
				title       => $journey->name,
				hide_opts   => 1,
				with_map    => 1,
				ajax_req    => "${trip_id}/${line_no}",
				ajax_route  => route_to_ajax(
					map {
						{
							name          => $_->loc->name,
							platform      => $_->platform,
							arr           => $_->arr,
							arr_cancelled => $_->arr_cancelled,
							arr_delay     => $_->arr_delay,
							dep           => $_->dep,
							dep_cancelled => $_->dep_cancelled,
							dep_delay     => $_->dep_delay,
						}
					} $journey->route
				),
				ajax_polyline => join(
					'|',
					map { join( ';', @{$_} ) } @{ $train_pos->{positions} }
				),
				origin => {
					name => ( $journey->route )[0]->loc->name,
					ts   => ( $journey->route )[0]->dep,
				},
				destination => {
					name => $journey->route_end,
					ts   => ( $journey->route )[-1]->arr,
				},
				train_no => $journey->number
				? ( $journey->type // q{} . ' ' . $journey->number )
				: undef,
				operator        => $journey->operator,
				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 ajax_route_efa {
	my ($self)  = @_;
	my $backend = $self->param('efa');
	my $trip_id = $self->stash('tripid');

	my $stopseq;
	if ( $trip_id =~ m{ ^ ([^@]*) @ ([^@]*) [(] ([^)]*) [)] (.*)  $ }x ) {
		$stopseq = {
			stateless => $1,
			stop_id   => $2,
			date      => $3,
			key       => $4
		};
	}
	else {
		$self->render(
			'_error',
			error => "cannot parse trip ID: $trip_id",
		);
		return;
	}

	$self->efa->get_polyline_p(
		stopseq => $stopseq,
		service => $backend
	)->then(
		sub {
			my ($trip) = @_;

			my $now = DateTime->now( time_zone => 'Europe/Berlin' );

			my @polyline
			  = map { { lat => $_->[0], lon => $_->[1] } }
			  $trip->polyline( fallback => 1 );
			my @route = $trip->route;

			my $ref_route = [
				map {
					{
						name      => $_->full_name,
						platform  => $_->platform,
						arr       => $_->arr,
						dep       => $_->dep,
						arr_delay => $_->arr_delay,
						dep_delay => $_->dep_delay,
						lat       => $_->latlon->[0],
						lon       => $_->latlon->[1]
					}
				} @route
			];
			$self->estimate_polyline_stops( \@polyline, $ref_route );

			my $train_pos = $self->estimate_train_positions2(
				now      => $now,
				route    => $ref_route,
				polyline => \@polyline,
			);

			$self->render(
				'_map_infobox',
				ajax_req      => "${trip_id}/0",
				ajax_route    => route_to_ajax( @{$ref_route} ),
				ajax_polyline => join( '|',
					map { join( ';', @{$_} ) } @{ $train_pos->{positions} } ),
				origin => {
					name => ( $trip->route )[0]->full_name,
					ts   => ( $trip->route )[0]->dep,
				},
				destination => {
					name => ( $trip->route )[-1]->full_name,
					ts   => ( $trip->route )[-1]->arr,
				},
				train_no => $trip->number
				? ( $trip->type // q{} . ' ' . $trip->number )
				: undef,
				next_stop => $train_pos->{next_stop},
			);
		}
	)->catch(
		sub {
			sub {
				my ($err) = @_;
				$self->render(
					'_error',
					error => $err,
				);
			}
		}
	)->wait;
}

sub ajax_route {
	my ($self) = @_;

	delete $self->stash->{layout};

	$self->render_later;

	if ( $self->param('efa') ) {
		return $self->ajax_route_efa;
	}

	my $trip_id = $self->stash('tripid');
	my $line_no = $self->stash('lineno');
	my $hafas   = $self->param('hafas');

	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) = @_;

			my $now = DateTime->now( time_zone => 'Europe/Berlin' );

			my @route    = $journey->route;
			my @polyline = $journey->polyline;

			my $train_pos = $self->estimate_train_positions2(
				now   => $now,
				route => [
					map {
						{
							name      => $_->loc->name,
							arr       => $_->arr,
							dep       => $_->dep,
							arr_delay => $_->arr_delay,
							dep_delay => $_->dep_delay,
							lat       => $_->loc->lat,
							lon       => $_->loc->lon
						}
					} @route
				],
				polyline => \@polyline,
			);

			$self->render(
				'_map_infobox',
				ajax_req   => "${trip_id}/${line_no}",
				ajax_route => route_to_ajax(
					map {
						{
							name          => $_->loc->name,
							platform      => $_->platform,
							arr           => $_->arr,
							arr_cancelled => $_->arr_cancelled,
							arr_delay     => $_->arr_delay,
							dep           => $_->dep,
							dep_cancelled => $_->dep_cancelled,
							dep_delay     => $_->dep_delay,
						}
					} @route
				),
				ajax_polyline => join(
					'|',
					map { join( ';', @{$_} ) } @{ $train_pos->{positions} }
				),
				origin => {
					name => ( $journey->route )[0]->loc->name,
					ts   => ( $journey->route )[0]->dep,
				},
				destination => {
					name => $journey->route_end,
					ts   => ( $journey->route )[-1]->arr,
				},
				train_no => $journey->number
				? ( $journey->type . ' ' . $journey->number )
				: undef,
				next_stop => $train_pos->{next_stop},
			);
		}
	)->catch(
		sub {
			my ($err) = @_;
			$self->render(
				'_error',
				error => $err,
			);
		}
	)->wait;
}

sub coverage {
	my ($self)  = @_;
	my $backend = lc( $self->stash('backend') );
	my $service = $self->stash('service');

	my $coverage = {};

	if ( $backend eq 'efa' ) {
		$coverage = $self->efa->get_coverage($service);
	}
	elsif ( $backend eq 'hafas' ) {
		$coverage = $self->hafas->get_coverage($service);
	}

	$self->render(
		'coverage_map',
		title     => "Abdeckung $service",
		hide_opts => 1,
		with_map  => 1,
		coverage  => encode_json($coverage),
	);
}

1;