diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index a009232..9ab597d 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -13,7 +13,8 @@ use File::Slurp qw(read_file); use Geo::Distance; use JSON; use List::Util; -use List::MoreUtils qw(after_incl before_incl); +use List::UtilsBy qw(uniq_by); +use List::MoreUtils qw(after_incl before_incl first_index); use Travel::Status::DE::DBWagenreihung; use Travel::Status::DE::IRIS; use Travel::Status::DE::IRIS::Stations; @@ -3554,6 +3555,206 @@ sub startup { } ); + $self->helper( + 'journeys_to_map_data' => sub { + my ( $self, %opt ) = @_; + + my @journeys = @{ $opt{journeys} // [] }; + my $route_type = $opt{route_type} // 'polybee'; + my $include_manual = $opt{include_manual} ? 1 : 0; + + my $location = $self->app->coordinates_by_station; + + my $with_polyline = $route_type eq 'beeline' ? 0 : 1; + + if ( not @journeys ) { + return { + skipped_journeys => [], + station_coordinates => [], + polyline_groups => [], + }; + } + + my $json = JSON->new->utf8; + + my $first_departure = $journeys[-1]->{rt_departure}; + my $last_departure = $journeys[0]->{rt_departure}; + + my @stations = List::Util::uniq map { $_->{to_name} } @journeys; + push( @stations, + List::Util::uniq map { $_->{from_name} } @journeys ); + @stations = List::Util::uniq @stations; + my @station_coordinates = map { [ $location->{$_}, $_ ] } + grep { exists $location->{$_} } @stations; + + my @station_pairs; + my @polylines; + my %seen; + + my @skipped_journeys; + my @polyline_journeys = grep { $_->{polyline} } @journeys; + my @beeline_journeys = grep { not $_->{polyline} } @journeys; + + if ( $route_type eq 'polyline' ) { + @beeline_journeys = (); + } + elsif ( $route_type eq 'beeline' ) { + push( @beeline_journeys, @polyline_journeys ); + @polyline_journeys = (); + } + + 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; + + if ( $from_index == -1 + or $to_index == -1 ) + { + # Fall back to route + delete $journey->{polyline}; + next; + } + + my $key + = $from_eva . '!' + . $to_eva . '!' + . ( $to_index - $from_index ); + + if ( $seen{$key} ) { + next; + } + + $seen{$key} = 1; + + # direction does not matter at the moment + $key + = $to_eva . '!' + . $from_eva . '!' + . ( $to_index - $from_index ); + $seen{$key} = 1; + + @polyline = @polyline[ $from_index .. $to_index ]; + my @polyline_coords; + for my $coord (@polyline) { + push( @polyline_coords, [ $coord->[1], $coord->[0] ] ); + } + push( @polylines, [@polyline_coords] ); + } + + for my $journey (@beeline_journeys) { + + my @route = map { $_->[0] } @{ $journey->{route} }; + + my $from_index + = first_index { $_ eq $journey->{from_name} } @route; + my $to_index = first_index { $_ eq $journey->{to_name} } @route; + + if ( $from_index == -1 ) { + my $rename = $self->app->renamed_station; + $from_index = first_index { + ( $rename->{$_} // $_ ) eq $journey->{from_name} + } + @route; + } + if ( $to_index == -1 ) { + my $rename = $self->app->renamed_station; + $to_index = first_index { + ( $rename->{$_} // $_ ) eq $journey->{to_name} + } + @route; + } + + if ( $from_index == -1 + or $to_index == -1 ) + { + push( @skipped_journeys, + [ $journey, 'Start/Ziel nicht in Route gefunden' ] ); + next; + } + + # Manual journey entries are only included if one of the following + # conditions is satisfied: + # * their route has more than two elements (-> probably more than just + # start and stop station), or + # * $include_manual is true (-> user wants to see incomplete routes) + # This avoids messing up the map in case an A -> B connection has been + # tracked both with a regular checkin (-> detailed route shown on map) + # 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 + and @route <= 2 + and not $include_manual ) + { + push( @skipped_journeys, + [ $journey, 'Manueller Eintrag ohne Unterwegshalte' ] ); + next; + } + + @route = @route[ $from_index .. $to_index ]; + + my $key = join( '|', @route ); + + if ( $seen{$key} ) { + next; + } + + $seen{$key} = 1; + + # direction does not matter at the moment + $seen{ join( '|', reverse @route ) } = 1; + + my $prev_station = shift @route; + for my $station (@route) { + push( @station_pairs, [ $prev_station, $station ] ); + $prev_station = $station; + } + } + + @station_pairs = uniq_by { $_->[0] . '|' . $_->[1] } @station_pairs; + @station_pairs = grep { + exists $location->{ $_->[0] } + and exists $location->{ $_->[1] } + } @station_pairs; + @station_pairs + = map { [ $location->{ $_->[0] }, $location->{ $_->[1] } ] } + @station_pairs; + + my @routes; + + my @lats = map { $_->[0][0] } @station_coordinates; + my @lons = map { $_->[0][1] } @station_coordinates; + my $min_lat = List::Util::min @lats; + my $max_lat = List::Util::max @lats; + my $min_lon = List::Util::min @lons; + my $max_lon = List::Util::max @lons; + + return { + skipped_journeys => \@skipped_journeys, + station_coordinates => \@station_coordinates, + polyline_groups => [ + { + polylines => $json->encode( \@station_pairs ), + color => '#673ab7', + opacity => $with_polyline ? 0.4 : 0.6, + }, + { + polylines => $json->encode( \@polylines ), + color => '#673ab7', + opacity => 0.8, + } + ], + bounds => [ [ $min_lat, $min_lon ], [ $max_lat, $max_lon ] ], + }; + } + ); + $self->helper( 'get_travel_distance' => sub { my ( $self, $journey ) = @_; diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index ff7df66..d830d2b 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -506,174 +506,18 @@ sub map_history { return; } - my $json = JSON->new->utf8; - my $include_manual = $self->param('include_manual') ? 1 : 0; - my $first_departure = $journeys[-1]->{rt_departure}; - my $last_departure = $journeys[0]->{rt_departure}; - - my @stations = uniq map { $_->{to_name} } @journeys; - push( @stations, uniq map { $_->{from_name} } @journeys ); - @stations = uniq @stations; - my @station_coordinates = map { [ $location->{$_}, $_ ] } - grep { exists $location->{$_} } @stations; - - my @station_pairs; - my @polylines; - my %seen; - - my @skipped_journeys; - my @polyline_journeys = grep { $_->{polyline} } @journeys; - my @beeline_journeys = grep { not $_->{polyline} } @journeys; - - if ( $route_type eq 'polyline' ) { - @beeline_journeys = (); - } - elsif ( $route_type eq 'beeline' ) { - push( @beeline_journeys, @polyline_journeys ); - @polyline_journeys = (); - } - - 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; - - if ( $from_index == -1 - or $to_index == -1 ) - { - # Fall back to route - delete $journey->{polyline}; - next; - } - - my $key = $from_eva . '!' . $to_eva . '!' . ( $to_index - $from_index ); - - if ( $seen{$key} ) { - next; - } - - $seen{$key} = 1; - - # direction does not matter at the moment - $key = $to_eva . '!' . $from_eva . '!' . ( $to_index - $from_index ); - $seen{$key} = 1; - - @polyline = @polyline[ $from_index .. $to_index ]; - my @polyline_coords; - for my $coord (@polyline) { - push( @polyline_coords, [ $coord->[1], $coord->[0] ] ); - } - push( @polylines, [@polyline_coords] ); - } - - for my $journey (@beeline_journeys) { - - my @route = map { $_->[0] } @{ $journey->{route} }; - - my $from_index = first_index { $_ eq $journey->{from_name} } @route; - my $to_index = first_index { $_ eq $journey->{to_name} } @route; - - if ( $from_index == -1 ) { - my $rename = $self->app->renamed_station; - $from_index - = first_index { ( $rename->{$_} // $_ ) eq $journey->{from_name} } - @route; - } - if ( $to_index == -1 ) { - my $rename = $self->app->renamed_station; - $to_index - = first_index { ( $rename->{$_} // $_ ) eq $journey->{to_name} } - @route; - } - - if ( $from_index == -1 - or $to_index == -1 ) - { - push( @skipped_journeys, - [ $journey, 'Start/Ziel nicht in Route gefunden' ] ); - next; - } - - # Manual journey entries are only included if one of the following - # conditions is satisfied: - # * their route has more than two elements (-> probably more than just - # start and stop station), or - # * $include_manual is true (-> user wants to see incomplete routes) - # This avoids messing up the map in case an A -> B connection has been - # tracked both with a regular checkin (-> detailed route shown on map) - # 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 - and @route <= 2 - and not $include_manual ) - { - push( @skipped_journeys, - [ $journey, 'Manueller Eintrag ohne Unterwegshalte' ] ); - next; - } - - @route = @route[ $from_index .. $to_index ]; - - my $key = join( '|', @route ); - - if ( $seen{$key} ) { - next; - } - - $seen{$key} = 1; - - # direction does not matter at the moment - $seen{ join( '|', reverse @route ) } = 1; - - my $prev_station = shift @route; - for my $station (@route) { - push( @station_pairs, [ $prev_station, $station ] ); - $prev_station = $station; - } - } - - @station_pairs = uniq_by { $_->[0] . '|' . $_->[1] } @station_pairs; - @station_pairs - = grep { exists $location->{ $_->[0] } and exists $location->{ $_->[1] } } - @station_pairs; - @station_pairs - = map { [ $location->{ $_->[0] }, $location->{ $_->[1] } ] } - @station_pairs; - - my @routes; - - my @lats = map { $_->[0][0] } @station_coordinates; - my @lons = map { $_->[0][1] } @station_coordinates; - my $min_lat = min @lats; - my $max_lat = max @lats; - my $min_lon = min @lons; - my $max_lon = max @lons; + my $res = $self->journeys_to_map_data( + journeys => \@journeys, + route_type => $route_type, + include_manual => $include_manual + ); $self->render( - template => 'history_map', - with_map => 1, - skipped_journeys => \@skipped_journeys, - station_coordinates => \@station_coordinates, - polyline_groups => [ - { - polylines => $json->encode( \@station_pairs ), - color => '#673ab7', - opacity => $with_polyline ? 0.4 : 0.6, - }, - { - polylines => $json->encode( \@polylines ), - color => '#673ab7', - opacity => 0.8, - } - ], - bounds => [ [ $min_lat, $min_lon ], [ $max_lat, $max_lon ] ], + template => 'history_map', + with_map => 1, + %{$res} ); }