From 9ef75520eeaf1a568650f7368782ac63b80c09b2 Mon Sep 17 00:00:00 2001 From: Birte Kristina Friesel Date: Tue, 26 Mar 2024 12:06:35 +0100 Subject: Preliminary support for stop-specific operators / operator changes (#10) --- bin/hafas-m | 35 ++++++- lib/Travel/Status/DE/HAFAS.pm | 24 +++++ lib/Travel/Status/DE/HAFAS/Journey.pm | 80 ++++++--------- lib/Travel/Status/DE/HAFAS/Product.pm | 181 ++++++++++++++++++++++++++++++++++ lib/Travel/Status/DE/HAFAS/Stop.pm | 24 ++++- 5 files changed, 287 insertions(+), 57 deletions(-) create mode 100644 lib/Travel/Status/DE/HAFAS/Product.pm diff --git a/bin/hafas-m b/bin/hafas-m index 67321d1..0dfda2a 100755 --- a/bin/hafas-m +++ b/bin/hafas-m @@ -384,6 +384,8 @@ elsif ( $opt{locationSearch} ) { } elsif ( $opt{journey} ) { my $result = $status->result; + my @prods; + my $prev_prod = 0; printf( "%s → %s", $result->name, $result->route_end ); if ( $result->number ) { @@ -392,7 +394,7 @@ elsif ( $opt{journey} ) { if ( $result->line_no ) { printf( " / Linie %s", $result->line_no ); } - printf( "\nFahrt %s am %s\n\n", + printf( "\nFahrt %s am %s\n", $result->id, ( $result->route )[0]->sched_dep->strftime('%d.%m.%Y') ); my $delay_len = 0; @@ -406,11 +408,24 @@ elsif ( $opt{journey} ) { { $occupancy_len = 2; } + my $prod = $stop->prod_dep // $stop->prod_arr; + if ( $prod and $prod != $prev_prod ) { + push( @prods, $prod ); + $prev_prod = $prod; + } } if ($delay_len) { $delay_fmt = $delay_len + 3; } + if ( @prods == 1 ) { + printf( "Betrieb: %s\n\n", $prev_prod->operator ); + } + else { + printf( "Betrieb: %s\n\n", join( q{, }, map { $_->operator } @prods ) ); + } + $prev_prod = 0; + my $now = DateTime->now( time_zone => 'Europe/Berlin' ); my $mark_stop = 0; for my $i ( reverse 1 .. scalar $result->route ) { @@ -440,8 +455,19 @@ elsif ( $opt{journey} ) { $msg_line .= sprintf( ' (%d)', $message->{id} ); } } + + my $prod_line = q{}; + if ( @prods > 1 ) { + my $prod = $stop->prod_dep // $stop->prod_arr; + if ( $prod and $prod != $prev_prod ) { + $prod_line + = sprintf( " : %s (%s)", $prod->name, $prod->operator ); + $prev_prod = $prod; + } + } + printf( -"%s%5s %s %5s %-${delay_fmt}s%${occupancy_len}s%-${occupancy_len}s %s%s%s%s\n", +"%s%5s %s %5s %-${delay_fmt}s%${occupancy_len}s%-${occupancy_len}s %s%s%s%s%s\n", $stop == $mark_stop ? $output_bold : q{}, $stop->arr_cancelled ? '--:--' : ( $stop->arr ? $stop->arr->strftime('%H:%M') : q{} ), @@ -454,8 +480,9 @@ elsif ( $opt{journey} ) { $stop->load->{SECOND} ? display_occupancy( $stop->load->{SECOND} ) : q{}, $stop->loc->name, - $stop == $mark_stop ? $output_reset : q{}, - $stop->direction ? sprintf( ' → %s', $stop->direction ) : q{}, + $stop == $mark_stop ? $output_reset : q{}, + $prod_line, + $stop->direction ? sprintf( ' → %s', $stop->direction ) : q{}, $msg_line, ); } diff --git a/lib/Travel/Status/DE/HAFAS.pm b/lib/Travel/Status/DE/HAFAS.pm index 0489901..cbd23cc 100644 --- a/lib/Travel/Status/DE/HAFAS.pm +++ b/lib/Travel/Status/DE/HAFAS.pm @@ -18,6 +18,7 @@ use Travel::Status::DE::HAFAS::Journey; use Travel::Status::DE::HAFAS::Location; use Travel::Status::DE::HAFAS::Message; use Travel::Status::DE::HAFAS::Polyline qw(decode_polyline); +use Travel::Status::DE::HAFAS::Product; use Travel::Status::DE::HAFAS::StopFinder; our $VERSION = '5.05'; @@ -702,6 +703,20 @@ sub add_message { return $message; } +sub parse_prodL { + my ($self) = @_; + + my $common = $self->{raw_json}{svcResL}[0]{res}{common}; + return [ + map { + Travel::Status::DE::HAFAS::Product->new( + common => $common, + product => $_ + ) + } @{ $common->{prodL} } + ]; +} + sub parse_search { my ($self) = @_; @@ -730,6 +745,8 @@ sub parse_journey { return $self; } + my $prodL = $self->parse_prodL; + my @locL = map { Travel::Status::DE::HAFAS::Location->new( loc => $_ ) } @{ $self->{raw_json}{svcResL}[0]{res}{common}{locL} // [] }; my $journey = $self->{raw_json}{svcResL}[0]{res}{journey}; @@ -748,6 +765,7 @@ sub parse_journey { $self->{result} = Travel::Status::DE::HAFAS::Journey->new( common => $self->{raw_json}{svcResL}[0]{res}{common}, + prodL => $prodL, locL => \@locL, journey => $journey, polyline => \@polyline, @@ -766,6 +784,8 @@ sub parse_journey_match { return $self; } + my $prodL = $self->parse_prodL; + my @locL = map { Travel::Status::DE::HAFAS::Location->new( loc => $_ ) } @{ $self->{raw_json}{svcResL}[0]{res}{common}{locL} // [] }; @@ -776,6 +796,7 @@ sub parse_journey_match { @{ $self->{results} }, Travel::Status::DE::HAFAS::Journey->new( common => $self->{raw_json}{svcResL}[0]{res}{common}, + prodL => $prodL, locL => \@locL, journey => $result, hafas => $self, @@ -794,6 +815,8 @@ sub parse_board { return $self; } + my $prodL = $self->parse_prodL; + my @locL = map { Travel::Status::DE::HAFAS::Location->new( loc => $_ ) } @{ $self->{raw_json}{svcResL}[0]{res}{common}{locL} // [] }; my @jnyL = @{ $self->{raw_json}{svcResL}[0]{res}{jnyL} // [] }; @@ -803,6 +826,7 @@ sub parse_board { @{ $self->{results} }, Travel::Status::DE::HAFAS::Journey->new( common => $self->{raw_json}{svcResL}[0]{res}{common}, + prodL => $prodL, locL => \@locL, journey => $result, hafas => $self, diff --git a/lib/Travel/Status/DE/HAFAS/Journey.pm b/lib/Travel/Status/DE/HAFAS/Journey.pm index 77d5748..6a36c22 100644 --- a/lib/Travel/Status/DE/HAFAS/Journey.pm +++ b/lib/Travel/Status/DE/HAFAS/Journey.pm @@ -26,13 +26,12 @@ Travel::Status::DE::HAFAS::Journey->mk_ro_accessors( sub new { my ( $obj, %opt ) = @_; - my @prodL = @{ $opt{common}{prodL} // [] }; - my @opL = @{ $opt{common}{opL} // [] }; my @icoL = @{ $opt{common}{icoL} // [] }; my @tcocL = @{ $opt{common}{tcocL} // [] }; my @remL = @{ $opt{common}{remL} // [] }; my @himL = @{ $opt{common}{himL} // [] }; + my $prodL = $opt{prodL}; my $locL = $opt{locL}; my $hafas = $opt{hafas}; my $journey = $opt{journey}; @@ -45,34 +44,7 @@ sub new { my $is_cancelled = $journey->{isCncl}; my $partially_cancelled = $journey->{isPartCncl}; - my $product = $prodL[ $journey->{prodX} ]; - my $name = $product->{addName} // $product->{name}; - my $line_no = $product->{prodCtx}{line}; - my $train_no = $product->{prodCtx}{num}; - my $cat = $product->{prodCtx}{catOut}; - my $catlong = $product->{prodCtx}{catOutL}; - if ( $name and $cat and $name eq $cat and $product->{nameS} ) { - $name .= ' ' . $product->{nameS}; - } - if ( defined $train_no and not $train_no ) { - $train_no = undef; - } - if ( - not defined $line_no - and defined $product->{prodCtx}{matchId} - and - ( not defined $train_no or $product->{prodCtx}{matchId} ne $train_no ) - ) - { - $line_no = $product->{prodCtx}{matchId}; - } - - my $operator; - if ( defined $product->{oprX} ) { - if ( my $opref = $opL[ $product->{oprX} ] ) { - $operator = $opref->{name}; - } - } + my $product = $prodL->[ $journey->{prodX} ]; my @messages; for my $msg ( @{ $journey->{msgL} // [] } ) { @@ -89,25 +61,33 @@ sub new { my $datetime_ref; - if ( @{ $journey->{stopL} // [] } or $journey->{stbStop}) { - my ($date_ref, $parse_fmt); - if ($jid =~ /#/) { + if ( @{ $journey->{stopL} // [] } or $journey->{stbStop} ) { + my ( $date_ref, $parse_fmt ); + if ( $jid =~ /#/ ) { + # ÖBB Journey ID - technically we ought to use Europe/Vienna tz # but let's not get into that... - $date_ref = ( split( /#/, $jid ) )[12]; + $date_ref = ( split( /#/, $jid ) )[12]; $parse_fmt = '%d%m%y'; if ( length($date_ref) < 5 ) { - warn("HAFAS, not even once -- midnight crossing may be bogus -- date_ref $date_ref"); - } elsif ( length($date_ref) == 5 ) { + warn( +"HAFAS, not even once -- midnight crossing may be bogus -- date_ref $date_ref" + ); + } + elsif ( length($date_ref) == 5 ) { $date_ref = "0${date_ref}"; } - } else { + } + else { # DB Journey ID - $date_ref = ( split( qr{[|]}, $jid ) )[4]; + $date_ref = ( split( qr{[|]}, $jid ) )[4]; $parse_fmt = '%d%m%Y'; if ( length($date_ref) < 7 ) { - warn("HAFAS, not even once -- midnight crossing may be bogus -- date_ref $date_ref"); - } elsif ( length($date_ref) == 7 ) { + warn( +"HAFAS, not even once -- midnight crossing may be bogus -- date_ref $date_ref" + ); + } + elsif ( length($date_ref) == 7 ) { $date_ref = "0${date_ref}"; } } @@ -117,8 +97,6 @@ sub new { )->parse_datetime($date_ref); } - my $class = $product->{cls}; - my @stops; my $route_end; for my $stop ( @{ $journey->{stopL} // [] } ) { @@ -128,6 +106,7 @@ sub new { loc => $loc, stop => $stop, common => $opt{common}, + prodL => $prodL, hafas => $hafas, date => $date, datetime_ref => $datetime_ref, @@ -150,14 +129,15 @@ sub new { my $ref = { id => $jid, - name => $name, - number => $train_no, - line => $name, - line_no => $line_no, - type => $cat, - type_long => $catlong, - class => $class, - operator => $operator, + product => $product, + name => $product->name, + number => $product->number, + line => $product->name, + line_no => $product->line_no, + type => $product->type, + type_long => $product->type_long, + class => $product->class, + operator => $product->operator, direction => $direction, is_cancelled => $is_cancelled, is_partially_cancelled => $partially_cancelled, diff --git a/lib/Travel/Status/DE/HAFAS/Product.pm b/lib/Travel/Status/DE/HAFAS/Product.pm new file mode 100644 index 0000000..5e45232 --- /dev/null +++ b/lib/Travel/Status/DE/HAFAS/Product.pm @@ -0,0 +1,181 @@ +package Travel::Status::DE::HAFAS::Product; + +# vim:foldmethod=marker + +use strict; +use warnings; +use 5.014; + +use parent 'Class::Accessor'; + +our $VERSION = '5.05'; + +Travel::Status::DE::HAFAS::Product->mk_ro_accessors( + qw(name type type_long class number line line_no operator) +); + +# {{{ Constructor + +sub new { + my ( $obj, %opt ) = @_; + + my $product = $opt{product}; + my $common = $opt{common}; + my $opL = $common->{opL}; + + my $class = $product->{cls}; + my $name = $product->{addName} // $product->{name}; + my $line_no = $product->{prodCtx}{line}; + my $train_no = $product->{prodCtx}{num}; + my $cat = $product->{prodCtx}{catOut}; + my $catlong = $product->{prodCtx}{catOutL}; + if ( $name and $cat and $name eq $cat and $product->{nameS} ) { + $name .= ' ' . $product->{nameS}; + } + if ( defined $train_no and not $train_no ) { + $train_no = undef; + } + if ( + not defined $line_no + and defined $product->{prodCtx}{matchId} + and + ( not defined $train_no or $product->{prodCtx}{matchId} ne $train_no ) + ) + { + $line_no = $product->{prodCtx}{matchId}; + } + + my $operator; + if ( defined $product->{oprX} ) { + if ( my $opref = $opL->[ $product->{oprX} ] ) { + $operator = $opref->{name}; + } + } + + my $ref = { + name => $name, + number => $train_no, + line => $name, + line_no => $line_no, + type => $cat, + type_long => $catlong, + class => $class, + operator => $operator, + }; + + bless( $ref, $obj ); + + return $ref; +} + +# }}} + +sub TO_JSON { + my ($self) = @_; + + return { %{$self} }; +} + +1; + +__END__ + +=head1 NAME + +Travel::Status::DE::HAFAS::Product - Information about a HAFAS product +associated with a journey. + +=head1 SYNOPSIS + +=head1 VERSION + +version 5.05 + +=head1 DESCRIPTION + +Travel::Status::DE::HAFAS::Product describes a product (e.g. train or bus) +associated with a Travel::Status::DE::HAFAS::Journey(3pm) or one of its +stops. + +=head1 METHODS + +=head2 ACCESSORS + +=over + +=item $product->name + +Journey or line name, either in a format like "Bus SB16" (Bus line +SB16) or "RE 10111" (RegionalExpress train 10111, no line information). May +contain extraneous whitespace characters. + +=item $product->type + +Type of this journey, e.g. "S" for S-Bahn, "RE" for Regional Express +or "STR" for tram / StraEenbahn. + +=item $product->type_long + +Long type of this journey, e.g. "S-Bahn" or "Regional-Express". + +=item $product->class + +An integer identifying the the mode of transport class. +Semantics depend on backend, e.g. "1" and "2" for long-distance trains and +"4" and "8" for regional trains. + +=item $product->line + +Journey or line name, either in a format like "Bus SB16" (Bus line +SB16), "RE 42" (RegionalExpress train 42) or "IC 2901" (InterCity train 2901, +no line information). May contain extraneous whitespace characters. Note that +this accessor does not return line information for IC/ICE/EC services, even if +it is available. Use B for those. + +=item $product->line_no + +Line identifier, or undef if it is unknown. +The line identifier may be a single number such as "11" (underground train +line U 11), a single word (e.g. "AIR") or a combination (e.g. "SB16"). +May also provide line numbers of IC/ICE services. + +=item $product->number + +Journey number (e.g. train number), or undef if it is unknown. + +=item $product->operator + +The operator responsible for this journey. Returns undef +if the backend does not provide an operator. + +Foo. + +=back + +=head1 DIAGNOSTICS + +None. + +=head1 DEPENDENCIES + +=over + +=item Class::Accessor(3pm) + +=back + +=head1 BUGS AND LIMITATIONS + +None known. + +=head1 SEE ALSO + +Travel::Status::DE::HAFAS(3pm). + +=head1 AUTHOR + +Copyright (C) 2024 by Birte Kristina Friesel Ederf@finalrewind.orgE + +=head1 LICENSE + +This module is licensed under the same terms as Perl itself. diff --git a/lib/Travel/Status/DE/HAFAS/Stop.pm b/lib/Travel/Status/DE/HAFAS/Stop.pm index 8c63a98..3b8391c 100644 --- a/lib/Travel/Status/DE/HAFAS/Stop.pm +++ b/lib/Travel/Status/DE/HAFAS/Stop.pm @@ -12,8 +12,8 @@ our $VERSION = '5.05'; Travel::Status::DE::HAFAS::Stop->mk_ro_accessors( qw(loc - rt_arr sched_arr arr arr_delay arr_cancelled - rt_dep sched_dep dep dep_delay dep_cancelled + rt_arr sched_arr arr arr_delay arr_cancelled prod_arr + rt_dep sched_dep dep dep_delay dep_cancelled prod_dep delay direction rt_platform sched_platform platform is_changed_platform is_additional @@ -28,6 +28,7 @@ sub new { my $stop = $opt{stop}; my $common = $opt{common}; + my $prodL = $opt{prodL}; my $date = $opt{date}; my $datetime_ref = $opt{datetime_ref}; my $hafas = $opt{hafas}; @@ -38,6 +39,11 @@ sub new { my $sched_dep = $stop->{dTimeS}; my $rt_dep = $stop->{dTimeR}; + my $prod_arr + = defined $stop->{aProdX} ? $prodL->[ $stop->{aProdX} ] : undef; + my $prod_dep + = defined $stop->{dProdX} ? $prodL->[ $stop->{dProdX} ] : undef; + # dIn. / aOut. -> may passengers enter / exit the train? my $sched_platform = $stop->{aPlatfS} // $stop->{dPlatfS}; @@ -100,11 +106,13 @@ sub new { arr => $rt_arr // $sched_arr, arr_delay => $arr_delay, arr_cancelled => $arr_cancelled, + prod_arr => $prod_arr, sched_dep => $sched_dep, rt_dep => $rt_dep, dep => $rt_dep // $sched_dep, dep_delay => $dep_delay, dep_cancelled => $dep_cancelled, + prod_dep => $prod_dep, delay => $dep_delay // $arr_delay, direction => $stop->{dDirTxt}, sched_platform => $sched_platform, @@ -254,12 +262,22 @@ Departure or arrival delay in minutes. Direction signage from this stop on, undef if unchanged. -=item $journey->messages +=item $stop->messages List of Travel::Status::DE::HAFAS::Message(3pm) instances related to this stop. These typically refer to delay reasons, platform changes, or changes in the line number / direction heading. +=item $stop->prod_arr + +Travel::Status::DE::HAFAS::Product(3pm) instance describing the transit product +(name, type, line number, operator, ...) upon arrival at this stop. + +=item $stop->prod_dep + +Travel::Status::DE::HAFAS::Product(3pm) instance describing the transit product +(name, type, line number, operator, ...) upon departure from this stop. + =item $stop->rt_platform Actual platform. -- cgit v1.2.3