diff options
-rw-r--r-- | .mailmap | 3 | ||||
-rw-r--r-- | Changelog | 29 | ||||
-rw-r--r-- | README.md | 33 | ||||
-rwxr-xr-x | bin/efa | 155 | ||||
-rw-r--r-- | lib/Travel/Routing/DE/EFA.pm | 174 | ||||
-rw-r--r-- | lib/Travel/Routing/DE/EFA/Route.pm | 6 | ||||
-rw-r--r-- | lib/Travel/Routing/DE/EFA/Route/Message.pm | 6 | ||||
-rw-r--r-- | lib/Travel/Routing/DE/EFA/Route/Part.pm | 32 | ||||
-rw-r--r-- | lib/Travel/Routing/DE/VRR.pm | 10 | ||||
-rwxr-xr-x | scripts/check-efa-urls | 9 | ||||
-rw-r--r-- | t/20-vrr.t | 177 | ||||
-rw-r--r-- | t/21-vrr.t | 2 |
12 files changed, 297 insertions, 339 deletions
diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..3c96bee --- /dev/null +++ b/.mailmap @@ -0,0 +1,3 @@ +Birte Kristina Friesel <derf@chaosdorf.de> +Birte Kristina Friesel <derf@finalrewind.org> +Birte Kristina Friesel <derf@derf.homelinux.org> @@ -1,3 +1,32 @@ +Travel::Routing::DE::VRR 2.24 - Thu Nov 23 2023 + + * efa: Make output less horizontally dense; show arrival and departure + delays in-line + * EFA::Route::Part: Add arrival_delay and departure_delay accessors + * EFA::Route::Part: via: Add delay field to returned list refs + +Travel::Routing::DE::VRR 2.23 - Mon Oct 02 2023 + + * Update default API endpoint as the previously used efa.vrr.de/vrr seems + to be gone for good + * Rename VRR2 API to VRR and VRR3 to VRR2 + +Travel::Routing::DE::VRR 2.22 - Fri Sep 01 2023 + + * Rewrite code not to use Perl's formerly experimental and now deprecated + smartmatch features + +Travel::Routing::DE::VRR 2.21 - Sat Jul 22 2023 + + * Add service VRR3 + * Update service URLs for VVO, VRN + * Remove discontinued services SVV, TLEM, VBL, Verbundlinie, VOR + +Travel::Routing::DE::VRR 2.20 - Fri Mar 26 2021 + + * EFA/Route/Part: Add occupancy accessor + * efa: Show expected occupancy, if available + Travel::Routing::DE::VRR 2.19 - Sat May 02 2020 * Switch from WTFPL to Perl license (GPL 1+ or Artistic) @@ -10,15 +10,13 @@ for details. efa has been packaged as [libtravel-routing-de-vrr-perl](https://packages.debian.org/search?keywords=libtravel-routing-de-vrr-perl) for Debian, so you can install it using your package manager of choice on -Debian-based Linux distributions. It is also available as -[perl-travel-routing-de-vrr-git](https://aur.archlinux.org/packages/perl-travel-routing-de-vrr-git/) -in the archlinux User Repository (AUR). Both provide the commandline client and -the Perl module. +Debian-based Linux distributions. The package provides the commandline client +as well as the Perl module. If you are using another distribution or would prefer a more recent version, you have four installation options: -* Nightly `.deb` builds for Debian-based distributions +* `.deb` releases for Debian-based distributions * Installing the latest release from CPAN * Installation from source * Using a Docker image @@ -27,37 +25,20 @@ Except for Docker, **efa** is available in your PATH after installation. You can run `efa --version` to verify this. Documentation is available via `man efa`. -### Nightly Builds for Debian +### Release Builds for Debian [lib.finalrewind.org/deb](https://lib.finalrewind.org/deb) provides Debian -packages of both development and release versions. Note that these are not part -of the official Debian repository and are not covered by its quality assurance -process. +packages of all release versions. Note that these are not part of the official +Debian repository and are not covered by its quality assurance process. To install the latest release, run: ``` wget https://lib.finalrewind.org/deb/libtravel-routing-de-vrr-perl_latest_all.deb -sudo dpkg -i libtravel-routing-de-vrr-perl_latest_all.deb -sudo apt --fix-broken install +sudo apt install ./libtravel-routing-de-vrr-perl_latest_all.deb rm libtravel-routing-de-vrr-perl_latest_all.deb ``` -For a (possibly broken) development snapshot of the Git master branch, run: - -``` -wget https://lib.finalrewind.org/deb/libtravel-routing-de-vrr-perl_dev_all.deb -sudo dpkg -i libtravel-routing-de-vrr-perl_dev_all.deb -sudo apt --fix-broken install -rm libtravel-routing-de-vrr-perl_dev_all.deb -``` - -Note that dpkg, unlike apt, does not automatically install missing -dependencies. If a dependency is not satisfied yet, `dpkg -i` will complain -about unmet dependencies and bail out. `apt --fix-broken install` installs -these dependencies and also silently finishes the Travel::Routing::DE::VRR -installation. - Uninstallation works as usual: ``` @@ -4,20 +4,18 @@ use warnings; use 5.010; use utf8; -no if $] >= 5.018, warnings => 'experimental::smartmatch'; - use utf8; use Encode qw(decode); use Travel::Routing::DE::EFA; use Exception::Class; use Getopt::Long qw/:config no_ignore_case/; -use List::Util qw(first); +use List::Util qw(first max); -our $VERSION = '2.19'; +our $VERSION = '2.24'; my $ignore_info; my $efa; -my $efa_url = 'http://efa.vrr.de/vrr/XSLT_TRIP_REQUEST2'; +my $efa_url = 'https://app.vrr.de/vrrstd/XML_TRIP_REQUEST2'; my ( @from, @to, @via, $from_type, $to_type, $via_type ); my $opt = { 'efa-url' => \$efa_url, @@ -32,6 +30,9 @@ my $opt = { binmode( STDOUT, ':encoding(utf-8)' ); binmode( STDERR, ':encoding(utf-8)' ); +my $output_bold = -t STDOUT ? "\033[1m" : q{}; +my $output_reset = -t STDOUT ? "\033[0m" : q{}; + sub show_help { my ($exit_status) = @_; @@ -49,7 +50,7 @@ sub new_efa_by_url { origin => [ @from, $from_type ], destination => [ @to, $to_type ], - via => ( @via ? [ @via, $via_type ] : undef ), + via => ( @via ? [ @via, $via_type ] : undef ), arrival_time => $opt->{arrive}, departure_time => $opt->{depart}, @@ -172,6 +173,22 @@ sub format_footpath { return $str; } +sub format_delay_incl { + my ( $delay, $len ) = @_; + if ( $delay and $len ) { + return sprintf( "(%+${len}d)", $delay ); + } + return q{}; +} + +sub format_delay_excl { + my ( $delay, $len ) = @_; + if ( $delay and $len ) { + return sprintf( " %+${len}d ", $delay ); + } + return q{}; +} + sub display_routes { my (@routes) = @_; @@ -191,8 +208,19 @@ sub display_routes { } } + my $delay_len = 0; for my $c ( $route->parts ) { - display_connection($c); + if ( $c->departure_delay ) { + $delay_len + = max( $delay_len, length( $c->departure_delay ) + 1 ); + } + if ( $c->arrival_delay ) { + $delay_len = max( $delay_len, length( $c->arrival_delay ) + 1 ); + } + } + + for my $c ( $route->parts ) { + display_connection( $c, $delay_len ); } # last one needs to be shown separately @@ -211,22 +239,13 @@ sub display_routes { } sub display_connection { - my ($c) = @_; + my ( $c, $delay_len ) = @_; + + my $delay_fmt = $delay_len ? $delay_len + 2 : 0; if ( $c->is_cancelled ) { say '# FAHRT FÄLLT AUS'; } - elsif ( $c->delay ) { - printf( "# +%d, Plan: %s -> %s\n", - $c->delay, $c->departure_stime, $c->arrival_stime ); - } - - for my $note ( $c->regular_notes ) { - my $text = $note->summary; - if ( not( length $ignore_info and $text =~ /$ignore_info/i ) ) { - say "# $text"; - } - } my $occupancy = q{}; @@ -242,15 +261,6 @@ sub display_connection { } } - for my $notice ( $c->current_notes ) { - if ( $notice->subtitle ne $notice->subject ) { - printf( "# %s - %s\n", $notice->subtitle, $notice->subject ); - } - else { - printf( "# %s\n", $notice->subtitle ); - } - } - if ( $opt->{maps} ) { for my $m ( $c->departure_routemaps, $c->departure_stationmaps ) { say "# $m"; @@ -258,22 +268,52 @@ sub display_connection { } printf( - "%-5s ab %-30s %-20s %s\n", + "${output_bold}%s${output_reset} %s %s\n", + $c->train_line || $c->train_product, + $c->train_destination ? q{→} : q{ }, + $c->train_destination + ); + + printf( + "%-5s %-${delay_fmt}s ab %-30s\n", $c->departure_time, + format_delay_incl( $c->departure_delay, $delay_len ), $c->departure_stop_and_platform, - $c->train_line || $c->train_product, - $c->train_destination, ); if ( $opt->{'full-route'} ) { for my $via_stop ( $c->via ) { - printf( "%-5s %-30s %s\n", - $via_stop->[1], $via_stop->[2], $via_stop->[3] ); + printf( + "%-5s %-${delay_fmt}s %-30s %s\n", + $via_stop->[1], format_delay_excl( $via_stop->[4], $delay_len ), + $via_stop->[2], $via_stop->[3] + ); + } + } + + printf( + "%-5s %-${delay_fmt}s an %-30s %s\n", + $c->arrival_time, + format_delay_incl( $c->arrival_delay, $delay_len ), + $c->arrival_stop_and_platform, $occupancy + ); + + for my $notice ( $c->current_notes ) { + if ( $notice->subtitle ne $notice->subject ) { + printf( "# %s - %s\n", $notice->subtitle, $notice->subject ); + } + else { + printf( "# %s\n", $notice->subtitle ); + } + } + + for my $note ( $c->regular_notes ) { + my $text = $note->summary; + if ( not( length $ignore_info and $text =~ /$ignore_info/i ) ) { + say "# $text"; } } - printf( "%-5s an %-30s %s\n", - $c->arrival_time, $c->arrival_stop_and_platform, $occupancy ); print "\n"; if ( $opt->{'extended-info'} @@ -340,14 +380,23 @@ if ( $opt->{exclude} ) { @{ $opt->{exclude} } = split( qr{,}, join( q{,}, @{ $opt->{exclude} } ) ); } +my %accessibility_map = ( + s => 'without_solid_stairs', + 'no-stairs' => 'without_solid_stairs', + e => 'without_escalators', + 'no-escalators' => 'without_escalators', + E => 'without_elevators', + 'no-elevators' => 'without_elevators', + l => 'with_low_platform', + nf => 'with_low_platform', + 'low-platform' => 'with_low_platform', + w => 'with_wheelchair', + wheelchair => 'with_wheelchair', +); + for my $field ( @{ $opt->{accessibility} } ) { - given ($field) { - when ( [qw[s no-stairs]] ) { $opt->{without_solid_stairs} = 1 } - when ( [qw[e no-escalators]] ) { $opt->{without_escalators} = 1 } - when ( [qw[E no-elevators]] ) { $opt->{without_elevators} = 1 } - when ( [qw[l nf low-platform]] ) { $opt->{with_low_platform} = 1 } - when ( [qw[w wheelchair]] ) { $opt->{with_wheelchair} = 1 } - when ( [qw[i info]] ) { } # used for ignore_info default + if ( $accessibility_map{$field} ) { + $opt->{ $accessibility_map{$field} } = 1; } } @@ -405,16 +454,18 @@ for my $pair ( [ \@from, \$from_type ], [ \@via, \$via_type ], {$+{target}}x ) { - given ( $+{type} ) { - when ('addr') { ${ $pair->[1] } = 'address' } - default { ${ $pair->[1] } = $+{type} } + if ( $+{type} eq 'addr' ) { + ${ $pair->[1] } = 'address'; + } + else { + ${ $pair->[1] } = $+{type}; } } } if ( $opt->{service} ) { my $service = first { lc( $_->{shortname} ) eq lc( $opt->{service} ) } - Travel::Routing::DE::EFA::get_efa_urls(); + Travel::Routing::DE::EFA::get_efa_urls(); if ( not $service ) { printf STDERR ( "Error: Unknown service '%s'. See 'efa -l' for a " @@ -472,7 +523,7 @@ efa - Command line client for EFA-based public transit routing services =head1 VERSION -version 2.19 +version 2.24 =head1 DESCRIPTION @@ -551,7 +602,7 @@ entry points which did not return an error. =item B<-u>|B<--efa-url> I<url> URL to the EFA entry point, defaults to -L<http://efa.vrr.de/vrr/XSLT_TRIP_REQUEST2>. Depending on your location, some +L<https://app.vrr.de/vrrstd/XML_TRIP_REQUEST2>. Depending on your location, some I<url>s may contain more specific data than others. See Travel::Routing::DE::EFA(3pm) and the B<-l> option for alternatives. @@ -728,9 +779,9 @@ All vehicles must be wheelchair accessible. 0 Everything went well 1 Invalid arguments, see error message 2 Network error, unable to send request - 3 efa.vrr.de did not return any parsable data - 4 efa.vrr.de error: ambiguous input - 5 efa.vrr.de error: no connections found + 3 EFA did not return any parsable data + 4 EFA error: ambiguous input + 5 EFA error: no connections found 10 Unknown Travel::Routing::DE::EFA error 255 Other internal error @@ -789,7 +840,7 @@ to to request via -> to. =head1 AUTHOR -Copyright (C) 2009-2021 by Daniel Friesel E<lt>derf@finalrewind.orgE<gt> +Copyright (C) 2009-2023 by Birte Kristina Friesel E<lt>derf@finalrewind.orgE<gt> =head1 LICENSE diff --git a/lib/Travel/Routing/DE/EFA.pm b/lib/Travel/Routing/DE/EFA.pm index 7d1e551..80bfb68 100644 --- a/lib/Travel/Routing/DE/EFA.pm +++ b/lib/Travel/Routing/DE/EFA.pm @@ -5,9 +5,7 @@ use warnings; use 5.010; use utf8; -no if $] >= 5.018, warnings => "experimental::smartmatch"; - -use Carp qw(cluck); +use Carp qw(cluck); use Encode qw(encode); use Travel::Routing::DE::EFA::Route; use Travel::Routing::DE::EFA::Route::Message; @@ -42,7 +40,7 @@ use Exception::Class ( }, ); -our $VERSION = '2.19'; +our $VERSION = '2.24'; sub set_time { my ( $self, %conf ) = @_; @@ -176,17 +174,19 @@ sub number_of_trips { sub select_interchange_by { my ( $self, $prefer ) = @_; - given ($prefer) { - when ('speed') { $self->{post}->{routeType} = 'LEASTTIME' } - when ('waittime') { $self->{post}->{routeType} = 'LEASTINTERCHANGE' } - when ('distance') { $self->{post}->{routeType} = 'LEASTWALKING' } - default { - Travel::Routing::DE::EFA::Exception::Setup->throw( - option => 'select_interchange_by', - have => $prefer, - want => 'speed / waittime / distance', - ); - } + if ( $prefer eq 'speed' ) { $self->{post}->{routeType} = 'LEASTTIME' } + elsif ( $prefer eq 'waittime' ) { + $self->{post}->{routeType} = 'LEASTINTERCHANGE'; + } + elsif ( $prefer eq 'distance' ) { + $self->{post}->{routeType} = 'LEASTWALKING'; + } + else { + Travel::Routing::DE::EFA::Exception::Setup->throw( + option => 'select_interchange_by', + have => $prefer, + want => 'speed / waittime / distance', + ); } return; @@ -195,17 +195,15 @@ sub select_interchange_by { sub train_type { my ( $self, $include ) = @_; - given ($include) { - when ('local') { $self->{post}->{lineRestriction} = 403 } - when ('ic') { $self->{post}->{lineRestriction} = 401 } - when ('ice') { $self->{post}->{lineRestriction} = 400 } - default { - Travel::Routing::DE::EFA::Exception::Setup->throw( - option => 'train_type', - have => $include, - want => 'local / ic / ice', - ); - } + if ( $include eq 'local' ) { $self->{post}->{lineRestriction} = 403 } + elsif ( $include eq 'ic' ) { $self->{post}->{lineRestriction} = 401 } + elsif ( $include eq 'ice' ) { $self->{post}->{lineRestriction} = 400 } + else { + Travel::Routing::DE::EFA::Exception::Setup->throw( + option => 'train_type', + have => $include, + want => 'local / ic / ice', + ); } return; @@ -229,7 +227,7 @@ sub use_near_stops { sub walk_speed { my ( $self, $walk_speed ) = @_; - if ( $walk_speed ~~ [ 'normal', 'fast', 'slow' ] ) { + if ( $walk_speed =~ m{ ^ (?: normal | fast | slow ) $ }x ) { $self->{post}->{changeSpeed} = $walk_speed; } else { @@ -305,7 +303,7 @@ sub place { @{ $self->{post} }{ "place_${which}", "name_${which}" } = ( $place, $stop ); - if ( $type ~~ [qw[address poi stop]] ) { + if ( $type =~ m{ ^ (?: address | poi | stop ) $ }x ) { $self->{post}->{"type_${which}"} = $type; } @@ -660,6 +658,8 @@ sub parse_xml_part { $e_astime //= $e_atime; my $delay = $e_delay ? $e_delay->getAttribute('delayMinutes') : 0; + my $delay_arr + = $e_delay ? $e_delay->getAttribute('delayMinutesArr') : 0; my ( @dep_rms, @dep_sms, @arr_rms, @arr_sms ); @@ -675,8 +675,8 @@ sub parse_xml_part { } my $hash = { - delay => $delay, departure_date => $self->itddate_str($e_ddate), + departure_delay => $delay, departure_time => $self->itdtime_str($e_dtime), departure_sdate => $self->itddate_str($e_dsdate), departure_stime => $self->itdtime_str($e_dstime), @@ -687,6 +687,7 @@ sub parse_xml_part { train_product => $e_mot->getAttribute('productName'), train_destination => $e_mot->getAttribute('destination'), arrival_date => $self->itddate_str($e_adate), + arrival_delay => $delay_arr, arrival_time => $self->itdtime_str($e_atime), arrival_sdate => $self->itddate_str($e_asdate), arrival_stime => $self->itdtime_str($e_astime), @@ -716,8 +717,8 @@ sub parse_xml_part { $hash->{arrival_stationmaps} = \@arr_sms; for my $ve ( $e->findnodes($xp_via) ) { - my $e_vdate = ( $ve->findnodes($xp_date) )[-1]; - my $e_vtime = ( $ve->findnodes($xp_time) )[-1]; + my $e_vdate = ( $ve->findnodes($xp_date) )[0]; + my $e_vtime = ( $ve->findnodes($xp_time) )[0]; if ( not( $e_vdate and $e_vtime ) or ( $e_vdate->getAttribute('weekday') == -1 ) ) @@ -725,10 +726,13 @@ sub parse_xml_part { next; } - my $name = $ve->getAttribute('name'); - my $platform = $ve->getAttribute('platformName'); + my $name = $ve->getAttribute('name'); + my $platform = $ve->getAttribute('platformName'); + my $arr_delay = $ve->getAttribute('arrDelay'); - if ( $name ~~ [ $hash->{departure_stop}, $hash->{arrival_stop} ] ) { + if ( $name eq $hash->{departure_stop} + or $name eq $hash->{arrival_stop} ) + { next; } @@ -738,7 +742,8 @@ sub parse_xml_part { $self->itddate_str($e_vdate), $self->itdtime_str($e_vtime), $name, - $platform + $platform, + $arr_delay, ] ); } @@ -820,7 +825,7 @@ sub check_ambiguous_xml { if ( $s_place eq 'list' ) { Travel::Routing::DE::EFA::Exception::Ambiguous->throw( - post_key => 'place', + post_key => 'place', post_value => ( $e_place->findnodes($xp_place_input) )[0]->textContent, possibilities => join( q{ | }, @@ -830,7 +835,7 @@ sub check_ambiguous_xml { } if ( $s_name eq 'list' ) { Travel::Routing::DE::EFA::Exception::Ambiguous->throw( - post_key => 'name', + post_key => 'name', post_value => ( $e_name->findnodes($xp_name_input) )[0]->textContent, possibilities => join( q{ | }, @@ -899,40 +904,11 @@ sub get_efa_urls { name => 'Nahverkehrsgesellschaft Baden-Württemberg', shortname => 'NVBW', }, - - # HTTPS not supported - { - url => 'http://efa.svv-info.at/sbs/XSLT_TRIP_REQUEST2', - name => 'Salzburger Verkehrsverbund', - shortname => 'SVV', - }, - - # HTTPS: invalid certificate - { - url => - 'http://www.travelineeastmidlands.co.uk/em/XSLT_TRIP_REQUEST2', - name => 'Traveline East Midlands', - shortname => 'TLEM', - }, { url => 'https://efa.vagfr.de/vagfr3/XSLT_TRIP_REQUEST2', name => 'Freiburger Verkehrs AG', shortname => 'VAG', }, - - # HTTPS: unsupported protocol - { - url => 'http://mobil.vbl.ch/vblmobil/XML_TRIP_REQUEST2', - name => 'Verkehrsbetriebe Luzern', - shortname => 'VBL', - }, - - # HTTPS not supported - { - url => 'http://fahrplan.verbundlinie.at/stv/XSLT_TRIP_REQUEST2', - name => 'Verkehrsverbund Steiermark', - shortname => 'Verbundlinie', - }, { url => 'https://efa.vgn.de/vgnExt_oeffi/XML_TRIP_REQUEST2', name => 'Verkehrsverbund Grossraum Nuernberg', @@ -946,32 +922,22 @@ sub get_efa_urls { shortname => 'VMV', }, { - url => 'https://efa.vor.at/wvb/XSLT_TRIP_REQUEST2', - name => 'Verkehrsverbund Ost-Region', - shortname => 'VOR', - encoding => 'iso-8859-15', - }, - - # HTTPS not spported - { - url => 'http://fahrplanauskunft.vrn.de/vrn/XML_TRIP_REQUEST2', + url => 'https://www.vrn.de/mngvrn/XML_TRIP_REQUEST2', name => 'Verkehrsverbund Rhein-Neckar', shortname => 'VRN', }, { - url => 'https://efa.vrr.de/vrr/XSLT_TRIP_REQUEST2', + url => 'https://app.vrr.de/vrrstd/XML_TRIP_REQUEST2', name => 'Verkehrsverbund Rhein-Ruhr', shortname => 'VRR', }, { - url => 'https://app.vrr.de/vrrstd/XML_TRIP_REQUEST2', + url => 'https://efa.vrr.de/rbgstd3/XSLT_TRIP_REQUEST2', name => 'Verkehrsverbund Rhein-Ruhr (alternative)', shortname => 'VRR2', }, - - # HTTPS not supported { - url => 'http://efa.vvo-online.de:8080/dvb/XSLT_TRIP_REQUEST2', + url => 'https://efa.vvo-online.de/VMSSL3/XSLT_TRIP_REQUEST2', name => 'Verkehrsverbund Oberelbe', shortname => 'VVO', }, @@ -996,7 +962,7 @@ Travel::Routing::DE::EFA - unofficial interface to EFA-based itinerary services use Travel::Routing::DE::EFA; my $efa = Travel::Routing::DE::EFA->new( - efa_url => 'http://efa.vrr.de/vrr/XSLT_TRIP_REQUEST2', + efa_url => 'https://app.vrr.de/vrrstd/XML_TRIP_REQUEST2';', origin => [ 'Essen', 'HBf' ], destination => [ 'Duisburg', 'HBf' ], ); @@ -1015,7 +981,7 @@ Travel::Routing::DE::EFA - unofficial interface to EFA-based itinerary services =head1 VERSION -version 2.19 +version 2.24 =head1 DESCRIPTION @@ -1042,47 +1008,9 @@ Valid hash keys and their values are: =item B<efa_url> => I<efa_url> Mandatory. Sets the entry point to the EFA itinerary service. -The following URLs (grouped by country) are known. A service marked with [!] -is not completely supported yet and may not work at all. - -=over - -=item * Austria - -=over - -=item * L<http://efa.ivb.at/ivb/XSLT_TRIP_REQUEST2> (Innsbrucker Verkehrsbetriebe) - -=item * L<http://efa.svv-info.at/sbs/XSLT_TRIP_REQUEST2> (Salzburger Verkehrsverbund) - -=item * L<http://efa.vor.at/wvb/XSLT_TRIP_REQUEST2> (Verkehrsverbund Ost-Region) - -=item * L<http://efaneu.vmobil.at/vvv/XSLT_TRIP_REQUEST2> (Vorarlberger Verkehrsverbund) - -=item * L<http://www.linzag.at/static/XSLT_TRIP_REQUEST2> (Linz AG) B<[!]> - -=item * The STV / Verkehrsverbund Steiermark is not supported since it returns -data with broken encoding - -=back - -=item * Germany - -=over - -=item * L<http://212.114.197.7/vgnExt_oeffi/XML_TRIP_REQUEST2> (Verkehrsverbund GroE<szlig>raum NE<uuml>rnberg) - -=item * L<http://efa.vrr.de/vrr/XSLT_TRIP_REQUEST2> (Verkehrsverbund Rhein-Ruhr) - -=item * L<http://app.vrr.de/standard/XML_TRIP_REQUEST2> (Verkehrsverbund Rhein-Ruhr with support for B<--full-route>) - -=item * L<http://www2.vvs.de/vvs/XSLT_TRIP_REQUEST2> (Verkehrsverbund Stuttgart) - -=back - -=back +See C<< efa --list >> for a list of supported services. -If you found a URL not listed here, please send it to +If you found a URL not listed there, please send it to E<lt>derf@finalrewind.orgE<gt>. =item B<origin> => B<[> I<city>B<,> I<stop> [ B<,> I<type> ] B<]> @@ -1286,7 +1214,7 @@ None known. =head1 AUTHOR -Copyright (C) 2009-2021 by Daniel Friesel E<lt>derf@finalrewind.orgE<gt> +Copyright (C) 2009-2023 by Birte Kristina Friesel E<lt>derf@finalrewind.orgE<gt> =head1 LICENSE diff --git a/lib/Travel/Routing/DE/EFA/Route.pm b/lib/Travel/Routing/DE/EFA/Route.pm index 61a7c31..87d4e4b 100644 --- a/lib/Travel/Routing/DE/EFA/Route.pm +++ b/lib/Travel/Routing/DE/EFA/Route.pm @@ -8,7 +8,7 @@ use parent 'Class::Accessor'; use Travel::Routing::DE::EFA::Route::Part; -our $VERSION = '2.19'; +our $VERSION = '2.24'; Travel::Routing::DE::EFA::Route->mk_ro_accessors( qw(duration ticket_text ticket_type fare_adult fare_child vehicle_time)); @@ -52,7 +52,7 @@ Travel::Routing::DE::EFA::Route - Single route (connection) between two points =head1 VERSION -version 2.19 +version 2.24 =head1 DESCRIPTION @@ -114,7 +114,7 @@ Travel::Routing::DE::EFA(3pm), Travel::Routing::DE::EFA::Route::Part(3pm). =head1 AUTHOR -Copyright (C) 2011-2015 by Daniel Friesel E<lt>derf@finalrewind.orgE<gt> +Copyright (C) 2011-2015 by Birte Kristina Friesel E<lt>derf@finalrewind.orgE<gt> =head1 LICENSE diff --git a/lib/Travel/Routing/DE/EFA/Route/Message.pm b/lib/Travel/Routing/DE/EFA/Route/Message.pm index ffa707c..6ae2e19 100644 --- a/lib/Travel/Routing/DE/EFA/Route/Message.pm +++ b/lib/Travel/Routing/DE/EFA/Route/Message.pm @@ -6,7 +6,7 @@ use 5.010; use parent 'Class::Accessor'; -our $VERSION = '2.19'; +our $VERSION = '2.24'; Travel::Routing::DE::EFA::Route::Message->mk_ro_accessors( qw(is_detailed summary subject subtitle raw_content)); @@ -58,7 +58,7 @@ route or route part. =head1 VERSION -version 2.19 +version 2.24 =head1 DESCRIPTION @@ -129,7 +129,7 @@ Travel::Routing::DE::EFA(3pm), Travel::Routing::DE::EFA::Route::Part(3pm). =head1 AUTHOR -Copyright (C) 2015 by Daniel Friesel E<lt>derf@finalrewind.orgE<gt> +Copyright (C) 2015 by Birte Kristina Friesel E<lt>derf@finalrewind.orgE<gt> =head1 LICENSE diff --git a/lib/Travel/Routing/DE/EFA/Route/Part.pm b/lib/Travel/Routing/DE/EFA/Route/Part.pm index 2c9df16..7ed2722 100644 --- a/lib/Travel/Routing/DE/EFA/Route/Part.pm +++ b/lib/Travel/Routing/DE/EFA/Route/Part.pm @@ -6,7 +6,7 @@ use 5.010; use parent 'Class::Accessor'; -our $VERSION = '2.19'; +our $VERSION = '2.24'; my %occupancy = ( MANY_SEATS => 1, @@ -16,14 +16,15 @@ my %occupancy = ( Travel::Routing::DE::EFA::Route::Part->mk_ro_accessors( qw(arrival_platform arrival_stop - arrival_date arrival_time arrival_sdate arrival_stime delay - departure_platform + arrival_date arrival_time arrival_sdate arrival_stime arrival_delay + delay + departure_platform departure_delay departure_stop departure_date departure_time departure_sdate departure_stime footpath_duration footpath_type occupancy train_destination train_line train_product - ) + ) ); sub new { @@ -38,6 +39,8 @@ sub new { delete $ref->{occupancy}; } + $ref->{delay} = $ref->{departure_delay}; + return bless( $ref, $obj ); } @@ -168,7 +171,7 @@ points, without interchanges =head1 VERSION -version 2.19 +version 2.24 =head1 DESCRIPTION @@ -188,6 +191,10 @@ included in the calculation, "Scheduled" means it isn't. =over +=item $part->arrival_delay + +arrival delay in minutes, 0 if unknown + =item $part->arrival_stop arrival stop (city name plus station name) @@ -237,7 +244,11 @@ Returns a list of Travel::Routing::DE::EFA::Route::Message(3pm) objects. =item $part->delay -delay in minutes, 0 if unknown +alias for departure_delay + +=item $part->departure_delay + +departure delay in minutes, 0 if unknown =item $part->departure_stop @@ -344,9 +355,10 @@ In those cases, B<train_destination> and B<train_line> are usually empty. =item $part->via -Returns a list of C<< [ "DD.MM.YYYY", "HH:MM", stop, platform ] >> arrayrefs -encoding the stops passed between B<departure_stop> and B<arrival_stop>, -if supported by the backend. Returns nothing / an empty list otherwise. +Returns a list of C<< [ "DD.MM.YYYY", "HH:MM", stop, platform, delay ] >> +arrayrefs encoding the stops passed between B<departure_stop> and +B<arrival_stop>, if supported by the backend. Returns nothing / an empty list +otherwise. Date and time refer to schedule data and do not account for delays. =back @@ -373,7 +385,7 @@ Class::Accessor(3pm). =head1 AUTHOR -Copyright (C) 2011-2021 by Daniel Friesel E<lt>derf@finalrewind.orgE<gt> +Copyright (C) 2011-2021 by Birte Kristina Friesel E<lt>derf@finalrewind.orgE<gt> =head1 LICENSE diff --git a/lib/Travel/Routing/DE/VRR.pm b/lib/Travel/Routing/DE/VRR.pm index 0ff291a..9e485d6 100644 --- a/lib/Travel/Routing/DE/VRR.pm +++ b/lib/Travel/Routing/DE/VRR.pm @@ -4,16 +4,14 @@ use strict; use warnings; use 5.010; -no if $] >= 5.018, warnings => "experimental::smartmatch"; - -our $VERSION = '2.19'; +our $VERSION = '2.24'; use parent 'Travel::Routing::DE::EFA'; sub new { my ( $class, %opt ) = @_; - $opt{efa_url} = 'http://efa.vrr.de/vrr/XSLT_TRIP_REQUEST2'; + $opt{efa_url} = 'https://app.vrr.de/vrrstd/XML_TRIP_REQUEST2'; return $class->SUPER::new(%opt); } @@ -49,7 +47,7 @@ Travel::Routing::DE::VRR - unofficial interface to the efa.vrr.de German itinera =head1 VERSION -version 2.19 +version 2.24 =head1 DESCRIPTION @@ -102,7 +100,7 @@ None known. =head1 AUTHOR -Copyright (C) 2009-2021 by Daniel Friesel E<lt>derf@finalrewind.orgE<gt> +Copyright (C) 2009-2021 by Birte Kristina Friesel E<lt>derf@finalrewind.orgE<gt> =head1 LICENSE diff --git a/scripts/check-efa-urls b/scripts/check-efa-urls index be3e4a4..804d949 100755 --- a/scripts/check-efa-urls +++ b/scripts/check-efa-urls @@ -7,17 +7,12 @@ IFS=',' checks="BSVG,Braunschweig,Hbf,Volkmarode,Moorhüttenweg DING,Ulm,Hbf,Ulm,Theodor-Heuss-Platz KVV,Karlsruhe,Hbf,Karlsruhe,Tivoli -LinzAG,Linz,Hbf,Linz,Hart +LinzAG,Linz/Donau,Hbf,Linz/Donau,Hart MVV,München,Donnersbergerbrücke,München,Seeholzenweg -NVBW,Stuttgart,Brendle (Großmarkt),Stuttgart,Schwabstraße -SVV,Salzburg,Hbf,Salzburg,Mirabellplatz -TLEM,London,Waterloo Underground Station,London,St Pancras Way +NVBW,Stuttgart,Brendle (Großmarkt),Stuttgart,Kleinhohenheim VAG,Schallstadt,Bf,Freiburg im Breisgau,Hbf -VBL,Luzern,Bf,Luzern,Brüggligasse -Verbundlinie,Graz,Hbf,Graz,Mariatrost VGN,Nürnberg,Hbf,Nürnberg,Messe VMV,Schwerin,Hbf,Schwerin,Dreescher Markt -VOR,Wien,Hbf,Wien,Praterstern VRN,Mannheim,Hbf,Ludwigshafen (Rhein),Hbf VRR,Essen,Hbf,Essen,Rüttenscheider Stern VRR2,Essen,Hbf,Essen,Rüttenscheider Stern @@ -13,208 +13,169 @@ require_ok('Travel::Routing::DE::VRR'); sub efa_conf { my $ret = { - efa_url => 'http://efa.vrr.de/vrr/XSLT_TRIP_REQUEST2', - origin => ['Essen', 'HBf'], - destination => ['Koeln', 'HBf'], - rm_base => 'http://efa.vrr.de/vrr/', - sm_base => 'http://efa.vrr.de/download/envmaps/', + efa_url => 'https://app.vrr.de/vrrstd/XML_TRIP_REQUEST2', + origin => [ 'Essen', 'HBf' ], + destination => [ 'Koeln', 'HBf' ], + rm_base => 'https://app.vrr.de/vrrstd/', + sm_base => 'https://app.vrr.de/download/envmaps/', lwp_options => {}, submit => 0, }; foreach my $p (@_) { - $ret->{$p->[0]} = $p->[1]; + $ret->{ $p->[0] } = $p->[1]; } return $ret; } sub efa_new { - return new_ok( - 'Travel::Routing::DE::VRR' => [%{efa_conf(@_)}] - ); + return new_ok( 'Travel::Routing::DE::VRR' => [ %{ efa_conf(@_) } ] ); } sub is_efa_post { - my ($ck, $cv, @post) = @_; - my $efa = efa_new([$ck, $cv]); + my ( $ck, $cv, @post ) = @_; + my $efa = efa_new( [ $ck, $cv ] ); my $ok = 1; is_deeply( - $efa->{'config'}, efa_conf([$ck, $cv]), + $efa->{'config'}, + efa_conf( [ $ck, $cv ] ), "$ck => $cv: conf ok", ); foreach my $ref (@post) { - my ($key, $value) = @{$ref}; - if (not defined $efa->{'post'}->{"key"} and - not defined $value) { + my ( $key, $value ) = @{$ref}; + if ( not defined $efa->{'post'}->{"key"} + and not defined $value ) + { next; } - if ($efa->{'post'}->{"$key"} ne $value) { + if ( $efa->{'post'}->{"$key"} ne $value ) { $ok = 0; last; } } - ok( - $ok, - "$ck => $cv: POST okay", - ); + ok( $ok, "$ck => $cv: POST okay", ); } sub is_efa_err { - my ($ck, $cv, $exception) = @_; + my ( $ck, $cv, $exception ) = @_; isa_ok( - exception { Travel::Routing::DE::VRR->new(%{efa_conf([$ck, $cv])}) }, + exception { + Travel::Routing::DE::VRR->new( %{ efa_conf( [ $ck, $cv ] ) } ) + }, $exception, "$ck => $cv" ); } -is_efa_post('ignored', 'ignored'); +is_efa_post( 'ignored', 'ignored' ); my $efa; is_efa_post( - 'via', ['MH', 'HBf'], - ['place_via', 'MH'], - ['name_via', 'HBf'], - ['type_via', 'stop'], + 'via', + [ 'MH', 'HBf' ], + [ 'place_via', 'MH' ], + [ 'name_via', 'HBf' ], + [ 'type_via', 'stop' ], ); is_efa_post( - 'origin', ['D', 'Fuerstenwall 232', 'address'], - ['place_origin', 'D'], - ['name_origin', 'Fuerstenwall 232'], - ['type_origin', 'address'], + 'origin', + [ 'D', 'Fuerstenwall 232', 'address' ], + [ 'place_origin', 'D' ], + [ 'name_origin', 'Fuerstenwall 232' ], + [ 'type_origin', 'address' ], ); is_efa_post( 'departure_time', '22:23', - ['itdTripDateTimeDepArr', 'dep'], - ['itdTimeHour', '22'], - ['itdTimeMinute', '23'], + [ 'itdTripDateTimeDepArr', 'dep' ], + [ 'itdTimeHour', '22' ], + [ 'itdTimeMinute', '23' ], ); is_efa_post( 'arrival_time', '16:38', - ['itdTripDateTimeDepArr', 'arr'], - ['itdTimeHour', '16'], - ['itdTimeMinute', '38'], + [ 'itdTripDateTimeDepArr', 'arr' ], + [ 'itdTimeHour', '16' ], + [ 'itdTimeMinute', '38' ], ); is_efa_post( 'date', '2.10.2009', - ['itdDateDay', '2'], - ['itdDateMonth', '10'], - ['itdDateYear', '2009'], + [ 'itdDateDay', '2' ], + [ 'itdDateMonth', '10' ], + [ 'itdDateYear', '2009' ], ); is_efa_post( 'date', '26.12.', - ['itdDateDay', '26'], - ['itdDateMonth', '12'], - ['itdDateYear', (localtime(time))[5] + 1900], + [ 'itdDateDay', '26' ], + [ 'itdDateMonth', '12' ], + [ 'itdDateYear', ( localtime(time) )[5] + 1900 ], ); -is_efa_post( - 'exclude', [qw[zug]], - ['inclMOT_0', undef], -); +is_efa_post( 'exclude', [qw[zug]], [ 'inclMOT_0', undef ], ); is_efa_post( 'exclude', [qw[stadtbus schiff ast]], - ['inclMOT_5', undef], - ['inclMOT_9', undef], - ['inclMOT_10', undef], + [ 'inclMOT_5', undef ], + [ 'inclMOT_9', undef ], + [ 'inclMOT_10', undef ], ); -is_efa_post( - 'select_interchange_by', 'speed', - ['routeType', 'LEASTTIME'], -); +is_efa_post( 'select_interchange_by', 'speed', [ 'routeType', 'LEASTTIME' ], ); -is_efa_post( - 'select_interchange_by', 'waittime', - ['routeType', 'LEASTINTERCHANGE'], +is_efa_post( 'select_interchange_by', 'waittime', + [ 'routeType', 'LEASTINTERCHANGE' ], ); -is_efa_post( - 'select_interchange_by', 'distance', - ['routeType', 'LEASTWALKING'], +is_efa_post( 'select_interchange_by', 'distance', + [ 'routeType', 'LEASTWALKING' ], ); -is_efa_post( - 'train_type', 'local', - ['lineRestriction', 403], -); +is_efa_post( 'train_type', 'local', [ 'lineRestriction', 403 ], ); -is_efa_post( - 'train_type', 'ic', - ['lineRestriction', 401], -); +is_efa_post( 'train_type', 'ic', [ 'lineRestriction', 401 ], ); -is_efa_post( - 'train_type', 'ice', - ['lineRestriction', 400], -); +is_efa_post( 'train_type', 'ice', [ 'lineRestriction', 400 ], ); -is_efa_post( - 'walk_speed', 'normal', - ['changeSpeed', 'normal'], -); +is_efa_post( 'walk_speed', 'normal', [ 'changeSpeed', 'normal' ], ); -is_efa_post( - 'max_interchanges', 5, - ['maxChanges', 5], -); +is_efa_post( 'max_interchanges', 5, [ 'maxChanges', 5 ], ); -is_efa_post( - 'use_near_stops', 1, - ['useProxFootSearch', 1], -); +is_efa_post( 'use_near_stops', 1, [ 'useProxFootSearch', 1 ], ); -is_efa_post( - 'with_bike', 1, - ['bikeTakeAlong', 1], -); +is_efa_post( 'with_bike', 1, [ 'bikeTakeAlong', 1 ], ); -is_efa_err( - 'departure_time', '37:00', +is_efa_err( 'departure_time', '37:00', 'Travel::Routing::DE::EFA::Exception::Setup', ); -is_efa_err( - 'departure_time', '07', +is_efa_err( 'departure_time', '07', 'Travel::Routing::DE::EFA::Exception::Setup', ); -is_efa_err( - 'train_type', 'invalid', +is_efa_err( 'train_type', 'invalid', 'Travel::Routing::DE::EFA::Exception::Setup', ); -is_efa_err( - 'walk_speed', 'invalid', +is_efa_err( 'walk_speed', 'invalid', 'Travel::Routing::DE::EFA::Exception::Setup', ); -is_efa_err( - 'select_interchange_by', 'invalid', +is_efa_err( 'select_interchange_by', 'invalid', 'Travel::Routing::DE::EFA::Exception::Setup', ); -is_efa_err( - 'exclude', [qw[sonstige invalid]], +is_efa_err( 'exclude', [qw[sonstige invalid]], 'Travel::Routing::DE::EFA::Exception::Setup', ); -is_efa_err( - 'date', '42.5.2003', - 'Travel::Routing::DE::EFA::Exception::Setup', -); +is_efa_err( 'date', '42.5.2003', + 'Travel::Routing::DE::EFA::Exception::Setup', ); -is_efa_err( - 'date', '7.', - 'Travel::Routing::DE::EFA::Exception::Setup', -); +is_efa_err( 'date', '7.', 'Travel::Routing::DE::EFA::Exception::Setup', ); @@ -78,7 +78,7 @@ is( $c0->arrival_time, '14:02', 'r0,0: artime' ); is( $c0->arrival_stime, '14:02', 'r0,0: astime' ); is_deeply( ($c0->via)[0], - ['27.11.2011', '13:56', 'Essen Florastr.', 'Bstg. 1'], + ['27.11.2011', '13:56', 'Essen Florastr.', 'Bstg. 1', undef], 'r0,0: via[0]'); is( $c1->delay, 3, 'r0,1: delay' ); |