summaryrefslogtreecommitdiff
path: root/lib/Travelynx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Travelynx')
-rw-r--r--lib/Travelynx/Command/translation.pm98
-rw-r--r--lib/Travelynx/Controller/Account.pm12
-rwxr-xr-xlib/Travelynx/Controller/Profile.pm9
-rwxr-xr-xlib/Travelynx/Controller/Traveling.pm10
-rwxr-xr-xlib/Travelynx/Model/Journeys.pm91
5 files changed, 191 insertions, 29 deletions
diff --git a/lib/Travelynx/Command/translation.pm b/lib/Travelynx/Command/translation.pm
new file mode 100644
index 0000000..70bdaba
--- /dev/null
+++ b/lib/Travelynx/Command/translation.pm
@@ -0,0 +1,98 @@
+package Travelynx::Command::translation;
+
+# Copyright (C) 2025 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+use Mojo::Base 'Mojolicious::Command';
+use Travelynx::Helper::Locales;
+
+has description => 'Export translation status';
+
+has usage => sub { shift->extract_usage };
+
+sub run {
+ my ( $self, $command ) = @_;
+
+ my @locales = (qw(de-DE en-GB fr-FR hu-HU pl-PL));
+
+ my %count;
+ my %handle;
+ for my $locale (@locales) {
+ $handle{$locale} = Travelynx::Helper::Locales->get_handle($locale);
+ $handle{$locale}->fail_with('failure_handler_auto');
+ $count{$locale} = 0;
+ }
+
+ binmode( STDOUT, ':encoding(utf-8)' );
+
+ if ( not $command ) {
+ $self->help;
+ }
+ elsif ( $command eq 'update-ref' ) {
+ my @buf;
+
+ open( my $fh, '<:encoding(utf-8)', 'share/locales/de_DE.po' );
+ my $comment;
+ for my $line (<$fh>) {
+ chomp $line;
+ if ( $line =~ m{ ^ [#] \s+ (.*) $ }x ) {
+ push( @buf, "## $1\n" );
+ }
+ elsif ( $line =~ m{ ^ [#] , \s+ (.*) $ }x ) {
+ $comment = $1;
+ }
+ elsif ( $line =~ m{ ^ msgid \s+ " (.*) " $ }x ) {
+ my $id = $1;
+ push( @buf, "### ${id}\n" );
+ if ($comment) {
+ push( @buf, '*' . $comment . "*\n" );
+ $comment = undef;
+ }
+ for my $locale (@locales) {
+ my $translation = $handle{$locale}->maketext($id);
+ if ( $translation ne $id ) {
+ push( @buf, "* ${locale}: ${translation}" );
+ $count{$locale} += 1;
+ }
+ else {
+ push( @buf, "* ${locale} *missing*" );
+ }
+ }
+ push( @buf, q{} );
+ }
+ }
+ close($fh);
+
+ open( $fh, '>:encoding(utf-8)', 'share/locales/reference.md' );
+ say $fh '# Translation Status';
+ say $fh q{};
+ for my $locale (@locales) {
+ say $fh sprintf(
+ '* %s: %.1f%% complete (%d missing)',
+ $locale,
+ $count{$locale} * 100 / $count{'de-DE'},
+ $count{'de-DE'} - $count{$locale},
+ );
+ }
+ say $fh q{};
+ for my $line (@buf) {
+ say $fh $line;
+ }
+ close($fh);
+ }
+ else {
+ $self->help;
+ }
+}
+
+1;
+
+__END__
+
+=head1 SYNOPSIS
+
+ Usage: index.pl dumpstops <format> <filename>
+
+ Exports known stops to <filename>.
+ Right now, only the "csv" format is supported.
diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm
index f4c6bcb..3a1e281 100644
--- a/lib/Travelynx/Controller/Account.pm
+++ b/lib/Travelynx/Controller/Account.pm
@@ -1119,6 +1119,7 @@ sub backend_form {
if (
$s->{coverage}{area}
and $s->{coverage}{area}{type} eq 'Polygon'
+ and defined $user_lon
and $self->lonlat_in_polygon(
$s->{coverage}{area}{coordinates},
[ $user_lon, $user_lat ]
@@ -1128,7 +1129,8 @@ sub backend_form {
push( @suggested_backends, $backend );
}
elsif ( $s->{coverage}{area}
- and $s->{coverage}{area}{type} eq 'MultiPolygon' )
+ and $s->{coverage}{area}{type} eq 'MultiPolygon'
+ and defined $user_lon )
{
for my $s_poly (
@{ $s->{coverage}{area}{coordinates} // [] } )
@@ -1189,6 +1191,7 @@ sub backend_form {
if (
$s->{coverage}{area}
and $s->{coverage}{area}{type} eq 'Polygon'
+ and defined $user_lon
and $self->lonlat_in_polygon(
$s->{coverage}{area}{coordinates},
[ $user_lon, $user_lat ]
@@ -1198,7 +1201,8 @@ sub backend_form {
push( @suggested_backends, $backend );
}
elsif ( $s->{coverage}{area}
- and $s->{coverage}{area}{type} eq 'MultiPolygon' )
+ and $s->{coverage}{area}{type} eq 'MultiPolygon'
+ and defined $user_lon )
{
for my $s_poly (
@{ $s->{coverage}{area}{coordinates} // [] } )
@@ -1240,6 +1244,7 @@ sub backend_form {
if (
$s->{coverage}{area}
and $s->{coverage}{area}{type} eq 'Polygon'
+ and defined $user_lon
and $self->lonlat_in_polygon(
$s->{coverage}{area}{coordinates},
[ $user_lon, $user_lat ]
@@ -1249,7 +1254,8 @@ sub backend_form {
push( @suggested_backends, $backend );
}
elsif ( $s->{coverage}{area}
- and $s->{coverage}{area}{type} eq 'MultiPolygon' )
+ and $s->{coverage}{area}{type} eq 'MultiPolygon'
+ and defined $user_lon )
{
for my $s_poly ( @{ $s->{coverage}{area}{coordinates} // [] } )
{
diff --git a/lib/Travelynx/Controller/Profile.pm b/lib/Travelynx/Controller/Profile.pm
index db30d36..978e3f8 100755
--- a/lib/Travelynx/Controller/Profile.pm
+++ b/lib/Travelynx/Controller/Profile.pm
@@ -114,7 +114,8 @@ sub profile {
my $map_data = {};
if ( $status->{checked_in} ) {
$map_data = $self->journeys_to_map_data(
- journeys => [$status],
+ journeys => [$status],
+ with_now_markers => 1,
);
}
@@ -506,7 +507,8 @@ sub user_status {
my $map_data = {};
if ( $status->{checked_in} ) {
$map_data = $self->journeys_to_map_data(
- journeys => [$status],
+ journeys => [$status],
+ with_now_markers => 1,
);
}
@@ -600,7 +602,8 @@ sub status_card {
if ( $status->{checked_in} ) {
$map_data = $self->journeys_to_map_data(
- journeys => [$status],
+ journeys => [$status],
+ with_now_markers => 1,
);
}
diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm
index a821f3a..e23301e 100755
--- a/lib/Travelynx/Controller/Traveling.pm
+++ b/lib/Travelynx/Controller/Traveling.pm
@@ -370,8 +370,9 @@ sub homepage {
my $map_data = {};
if ( $status->{arr_name} ) {
$map_data = $self->journeys_to_map_data(
- journeys => [$status],
- show_full_route => 1,
+ journeys => [$status],
+ show_full_route => 1,
+ with_now_markers => 1,
);
}
my $journey_visibility
@@ -461,8 +462,9 @@ sub status_card {
my $map_data = {};
if ( $status->{arr_name} ) {
$map_data = $self->journeys_to_map_data(
- journeys => [$status],
- show_full_route => 1,
+ journeys => [$status],
+ show_full_route => 1,
+ with_now_markers => 1,
);
}
my $journey_visibility
diff --git a/lib/Travelynx/Model/Journeys.pm b/lib/Travelynx/Model/Journeys.pm
index 5e6195f..9efa365 100755
--- a/lib/Travelynx/Model/Journeys.pm
+++ b/lib/Travelynx/Model/Journeys.pm
@@ -13,7 +13,7 @@ use DateTime;
use DateTime::Format::Strptime;
use GIS::Distance;
use JSON;
-use List::MoreUtils qw(after_incl before_incl);
+use List::MoreUtils qw(after_incl before_incl first_index last_index);
my %visibility_itoa = (
100 => 'public',
@@ -704,7 +704,7 @@ sub get {
for my $stop ( @{ $ref->{route} } ) {
for my $k (qw(rt_arr rt_dep sched_arr sched_dep)) {
if ( $stop->[2]{$k} ) {
- $stop->[2]{$k} = epoch_to_dt( $stop->[2]{$k} );
+ $stop->[2]{"${k}_dt"} = epoch_to_dt( $stop->[2]{$k} );
}
}
}
@@ -1190,9 +1190,11 @@ sub get_travel_distance {
my $from = $journey->{from_name};
my $from_eva = $journey->{from_eva};
my $from_latlon = $journey->{from_latlon};
+ my $from_ts = $journey->{sched_dep_ts} // $journey->{rt_dep_ts};
my $to = $journey->{to_name};
my $to_eva = $journey->{to_eva};
my $to_latlon = $journey->{to_latlon};
+ my $to_ts = $journey->{sched_arr_ts} // $journey->{rt_arr_ts};
my $route_ref = $journey->{route};
my $polyline_ref = $journey->{polyline};
@@ -1246,31 +1248,82 @@ sub get_travel_distance {
my $geo = GIS::Distance->new();
my $distance_beeline
= $geo->distance_metal( @{$from_latlon}, @{$to_latlon} );
- my @route
- = after_incl { ( $_->[1] and $_->[1] == $from_eva ) or $_->[0] eq $from }
+
+ # A trip may pass the same stop multiple times.
+ # Thus, two criteria must be met to select the start/end of the actual route:
+ # * stop name or ID matches, and
+ # * one of:
+ # - arrival/departure time at the stop matches, or
+ # - the stop does not have arrival/departure time
+ # In the latter case, we still face the risk of selecting the wrong
+ # start/end stop. However, we have no way of finding the right one. As the
+ # majority of trips do not pass the same stop multiple times, it's better
+ # to risk having a few inaccurate distances than not calculating the
+ # distance for any journey that lacks sched_dep/rt_dep or
+ # sched_from/rt_from.
+
+ my $route_start = first_index {
+ (
+ ( $_->[1] and $_->[1] == $from_eva or $_->[0] eq $from )
+ and ( not( defined $_->[2]{sched_dep} or defined $_->[2]{rt_dep} )
+ or ( $_->[2]{sched_dep} // $_->[2]{rt_dep} ) == $from_ts )
+ )
+ }
@{$route_ref};
- @route
- = before_incl { ( $_->[1] and $_->[1] == $to_eva ) or $_->[0] eq $to }
- @route;
- if (
- @route < 2
- or ( $route[-1][0] ne $to
- and ( not $route[-1][1] or $route[-1][1] != $to_eva ) )
- )
- {
+ # Here, we need to use last_index. In case of ring lines, the first index
+ # will not have sched_arr/rt_arr set, but we should not select it as route
+ # end...
+ my $route_end = last_index {
+ (
+ ( $_->[1] and $_->[1] == $to_eva or $_->[0] eq $to )
+ and ( not( defined $_->[2]{sched_arr} or defined $_->[2]{rt_arr} )
+ or ( $_->[2]{sched_arr} // $_->[2]{rt_arr} ) == $to_ts )
+ )
+ }
+ @{$route_ref};
- # I AM ERROR
+ if ( not defined $route_start and defined $route_end ) {
return ( 0, 0, $distance_beeline );
}
- my @polyline = after_incl { $_->[2] and $_->[2] == $from_eva }
+ my %seen;
+ for my $stop ( @{$route_ref} ) {
+ $seen{ $stop->[1] } //= 1;
+ $stop->[2]{n} = $seen{ $stop->[1] };
+ $seen{ $stop->[1] } += 1;
+ }
+
+ # Assumption: polyline entries are always [lat, lon] or [lat, lon, stop ID]
+ %seen = ();
+ for my $entry ( @{ $polyline_ref // [] } ) {
+ if ( $entry->[2] ) {
+ $seen{ $entry->[2] } //= 1;
+ $entry->[3] = $seen{ $entry->[2] };
+ $seen{ $entry->[2] } += 1;
+ }
+ }
+
+ $journey->{route_dep_index} = $route_start;
+ $journey->{route_arr_index} = $route_end;
+
+ my @route = @{$route_ref}[ $route_start .. $route_end ];
+
+ # Just like the route, the polyline may contain the same stop more than
+ # once. So we need to select based on the seen counter.
+ my $poly_start = first_index {
+ $_->[2] and $_->[2] == $from_eva and $_->[3] == $route[0][2]{n}
+ }
+ @{ $polyline_ref // [] };
+ my $poly_end = first_index {
+ $_->[2] and $_->[2] == $to_eva and $_->[3] == $route[-1][2]{n}
+ }
@{ $polyline_ref // [] };
- @polyline
- = before_incl { $_->[2] and $_->[2] == $to_eva } @polyline;
- # ensure that before_incl matched -- otherwise, @polyline is too long
- if ( @polyline and $polyline[-1][2] == $to_eva ) {
+ if ( defined $poly_start and defined $poly_end ) {
+ $journey->{poly_dep_index} = $poly_start;
+ $journey->{poly_arr_index} = $poly_end;
+ my @polyline = @{$polyline_ref}[ $poly_start .. $poly_end ];
my $prev_station = shift @polyline;
for my $station (@polyline) {
$distance_polyline += $geo->distance_metal(