File Coverage

blib/lib/Travel/Status/DE/EFA/Departure.pm
Criterion Covered Total %
statement 42 114 36.8
branch 6 50 12.0
condition 10 50 20.0
subroutine 10 18 55.5
pod 8 10 80.0
total 76 242 31.4


line stmt bran cond sub pod time code
1             package Travel::Status::DE::EFA::Departure;
2              
3 2     2   23 use strict;
  2         15  
  2         88  
4 2     2   14 use warnings;
  2         4  
  2         136  
5 2     2   43 use 5.010;
  2         8  
6              
7 2     2   11 use DateTime;
  2         18  
  2         119  
8 2     2   49 use List::Util qw(any);
  2         6  
  2         171  
9 2     2   1197 use Travel::Status::DE::EFA::Stop;
  2         10  
  2         13  
10              
11 2     2   106 use parent 'Class::Accessor';
  2         6  
  2         13  
12              
13             our $VERSION = '3.19';
14              
15             Travel::Status::DE::EFA::Departure->mk_ro_accessors(
16             qw(countdown datetime delay destination is_cancelled key line lineref mot
17             occupancy operator origin platform platform_db platform_name rt_datetime
18             sched_datetime stateless stop_id_num train_type train_name train_no type)
19             );
20              
21             my @mot_mapping = qw{
22             zug s-bahn u-bahn stadtbahn tram stadtbus regionalbus
23             schnellbus seilbahn schiff ast sonstige
24             };
25              
26             sub parse_departure {
27 0     0 0 0 my ( $self, $departure ) = @_;
28             }
29              
30             sub new {
31 40     40 1 157 my ( $obj, %conf ) = @_;
32              
33 40         64 my $departure = $conf{json};
34 40         51 my ( $sched_dt, $real_dt );
35              
36 40 50       96 if ( my $dt = $departure->{dateTime} ) {
37             $sched_dt = DateTime->new(
38             year => $dt->{year},
39             month => $dt->{month},
40             day => $dt->{day},
41             hour => $dt->{hour},
42             minute => $dt->{minute},
43 40   50     236 second => $dt->{second} // 0,
44             time_zone => 'Europe/Berlin',
45             );
46             }
47              
48 40 100       25744 if ( my $dt = $departure->{realDateTime} ) {
49             $real_dt = DateTime->new(
50             year => $dt->{year},
51             month => $dt->{month},
52             day => $dt->{day},
53             hour => $dt->{hour},
54             minute => $dt->{minute},
55 38   50     207 second => $dt->{second} // 0,
56             time_zone => 'Europe/Berlin',
57             );
58             }
59              
60             my @hints
61 40   50     23655 = map { $_->{content} } @{ $departure->{servingLine}{hints} // [] };
  0         0  
  40         240  
62              
63             my $ref = {
64             strp_stopseq_s => $conf{strp_stopseq_s},
65             strp_stopseq => $conf{strp_stopseq},
66             rt_datetime => $real_dt,
67             platform => $departure->{platform},
68             platform_name => $departure->{platformName},
69             platform_type => $departure->{pointType},
70             key => $departure->{servingLine}{key},
71             stateless => $departure->{servingLine}{stateless},
72             stop_id_num => $departure->{stopID},
73             line => $departure->{servingLine}{symbol}
74             || $departure->{servingLine}{number},
75             train_type => $departure->{servingLine}{trainType},
76             train_name => $departure->{servingLine}{trainName},
77             train_no => $departure->{servingLine}{trainNum},
78             origin => $departure->{servingLine}{directionFrom},
79             destination => $departure->{servingLine}{direction},
80             occupancy => $departure->{occupancy},
81             countdown => $departure->{countdown},
82             delay => $departure->{servingLine}{delay},
83             sched_datetime => $sched_dt,
84             type => $departure->{servingLine}{name},
85             mot => $departure->{servingLine}{motType},
86 40   33     810 hints => \@hints,
87             };
88              
89 40 50 66     174 if ( defined $ref->{delay} and $ref->{delay} eq '-9999' ) {
90 0         0 $ref->{delay} = 0;
91 0         0 $ref->{is_cancelled} = 1;
92             }
93             else {
94 40         55 $ref->{is_cancelled} = 0;
95             }
96              
97 40   66     119 $ref->{datetime} = $ref->{rt_datetime} // $ref->{sched_datetime};
98              
99 40         84 bless( $ref, $obj );
100              
101 40 50       73 if ( $departure->{prevStopSeq} ) {
102             $ref->{prev_route} = $ref->parse_route( $departure->{prevStopSeq},
103 0         0 $departure->{stopID} );
104             }
105 40 50       62 if ( $departure->{onwardStopSeq} ) {
106             $ref->{next_route} = $ref->parse_route( $departure->{onwardStopSeq},
107 0         0 $departure->{stopID} );
108             }
109              
110 40         235 return $ref;
111             }
112              
113             sub parse_route {
114 0     0 0 0 my ( $self, $stop_seq, $requested_id ) = @_;
115 0         0 my @ret;
116              
117 0 0       0 if ( not $stop_seq ) {
118 0         0 return \@ret;
119             }
120              
121             # Oh EFA, you so silly
122 0 0       0 if ( ref($stop_seq) eq 'HASH' ) {
123              
124             # For lines that start or terminate at the requested stop, onwardStopSeq / prevStopSeq includes the requested stop.
125 0 0       0 if ( $stop_seq->{ref}{id} eq $requested_id ) {
126 0         0 return \@ret;
127             }
128 0         0 $stop_seq = [$stop_seq];
129             }
130              
131 0   0     0 for my $stop ( @{ $stop_seq // [] } ) {
  0         0  
132 0         0 my $ref = $stop->{ref};
133 0         0 my ( $arr, $dep );
134              
135 0 0       0 if ( $ref->{arrDateTimeSec} ) {
    0          
136             $arr = $self->{strp_stopseq_s}
137 0         0 ->parse_datetime( $ref->{arrDateTimeSec} );
138             }
139             elsif ( $ref->{arrDateTime} ) {
140 0         0 $arr = $self->{strp_stopseq}->parse_datetime( $ref->{arrDateTime} );
141             }
142              
143 0 0       0 if ( $ref->{depDateTimeSec} ) {
    0          
144             $dep = $self->{strp_stopseq_s}
145 0         0 ->parse_datetime( $ref->{depDateTimeSec} );
146             }
147             elsif ( $ref->{depDateTime} ) {
148 0         0 $dep = $self->{strp_stopseq}->parse_datetime( $ref->{depDateTime} );
149             }
150              
151             push(
152             @ret,
153             Travel::Status::DE::EFA::Stop->new(
154             sched_arr => $arr,
155             sched_dep => $dep,
156             arr_delay => $ref->{arrValid} ? $ref->{arrDelay} : undef,
157             dep_delay => $ref->{depValid} ? $ref->{depDelay} : undef,
158             id_num => $ref->{id},
159             id_code => $ref->{gid},
160             full_name => $stop->{name},
161             place => $stop->{place},
162             name => $stop->{nameWO},
163             occupancy => $stop->{occupancy},
164             platform => $ref->{platform} || $stop->{platformName} || undef,
165             )
166 0 0 0     0 );
    0          
167             }
168              
169 0         0 return \@ret;
170             }
171              
172             sub id {
173 0     0 1 0 my ($self) = @_;
174              
175 0 0       0 if ( $self->{id} ) {
176 0         0 return $self->{id};
177             }
178              
179 0 0 0     0 return $self->{id} = sprintf( '%s@%d(%s)%d',
    0          
180             $self->stateless =~ s{ }{}gr,
181             scalar $self->route_pre ? ( $self->route_pre )[0]->id_num
182             : $self->stop_id_num,
183             ( scalar $self->route_pre and ( $self->route_pre )[0]->sched_dep )
184             ? ( $self->route_pre )[0]->sched_dep->strftime('%Y%m%dT%H:%M')
185             : $self->sched_datetime->strftime('%Y%m%dT%H:%M'),
186             $self->key );
187             }
188              
189             sub hints {
190 3     3 1 35818 my ($self) = @_;
191              
192 3   50     6 return @{ $self->{hints} // [] };
  3         25  
193             }
194              
195             sub mot_name {
196 3     3 1 12473 my ($self) = @_;
197              
198 3   50     49 return $mot_mapping[ $self->{mot} ] // 'sonstige';
199             }
200              
201             sub route_pre {
202 0     0 1   my ($self) = @_;
203              
204 0   0       return @{ $self->{prev_route} // [] };
  0            
205             }
206              
207             sub route_post {
208 0     0 1   my ($self) = @_;
209              
210 0   0       return @{ $self->{next_route} // [] };
  0            
211             }
212              
213             sub route_interesting {
214 0     0 1   my ( $self, $max_parts ) = @_;
215              
216 0           my @via = $self->route_post;
217 0           my ( @via_main, @via_show, $last_stop );
218 0   0       $max_parts //= 3;
219              
220 0           for my $stop (@via) {
221 0 0         if (
222             $stop->name =~ m{ Bf | Hbf | Flughafen | [Bb]ahnhof
223             | Krankenhaus | Klinik | (?: S $ ) }ox
224             )
225             {
226 0           push( @via_main, $stop );
227             }
228             }
229 0           $last_stop = pop(@via);
230              
231 0 0 0       if ( @via_main and $via_main[-1] == $last_stop ) {
232 0           pop(@via_main);
233             }
234 0 0 0       if ( @via and $via[-1] == $last_stop ) {
235 0           pop(@via);
236             }
237              
238 0 0 0       if ( @via_main and @via and $via[0] == $via_main[0] ) {
      0        
239 0           shift(@via_main);
240             }
241              
242 0 0         if ( @via < $max_parts ) {
243 0           @via_show = @via;
244             }
245             else {
246 0 0         if ( @via_main >= $max_parts ) {
247 0           @via_show = ( $via[0] );
248             }
249             else {
250 0           @via_show = splice( @via, 0, $max_parts - @via_main );
251             }
252              
253 0   0       while ( @via_show < $max_parts and @via_main ) {
254 0           my $stop = shift(@via_main);
255 0 0 0 0     if ( any { $_->name eq $stop->name } @via_show
  0            
256             or $stop->name eq $last_stop->name )
257             {
258 0           next;
259             }
260 0           push( @via_show, $stop );
261             }
262             }
263              
264 0           return @via_show;
265             }
266              
267             sub TO_JSON {
268 0     0 1   my ($self) = @_;
269              
270             # compute on-demand keys
271 0           $self->id;
272              
273 0           my $ret = { %{$self} };
  0            
274              
275 0           delete $ret->{strp_stopseq};
276 0           delete $ret->{strp_stopseq_s};
277              
278 0           for my $k (qw(datetime rt_datetime sched_datetime)) {
279 0 0         if ( $ret->{$k} ) {
280 0           $ret->{$k} = $ret->{$k}->epoch;
281             }
282             }
283              
284 0           return $ret;
285             }
286              
287             1;
288              
289             __END__
290              
291             =head1 NAME
292              
293             Travel::Status::DE::EFA::Departure - Information about a single
294             departure received by Travel::Status::DE::EFA
295              
296             =head1 SYNOPSIS
297              
298             for my $departure ($status->results) {
299             printf(
300             "At %s: %s to %s from platform %d\n",
301             $departure->datetime->strftime('%H:%M'), $departure->line,
302             $departure->destination, $departure->platform
303             );
304             }
305              
306             =head1 VERSION
307              
308             version 3.19
309              
310             =head1 DESCRIPTION
311              
312             Travel::Status::DE::EFA::Departure describes a single departure as obtained by
313             Travel::Status::DE::EFA. It contains information about the time, platform,
314             line number and destination.
315              
316             =head1 METHODS
317              
318             =head2 ACCESSORS
319              
320             =over
321              
322             =item $departure->countdown
323              
324             Time in minutes from now until the tram/bus/train will depart, including
325             realtime data if available.
326              
327             If delay information is available, it is already included.
328              
329             =item $departure->datetime
330              
331             DateTime(3pm) object for departure date and time. Realtime data if available,
332             schedule data otherwise.
333              
334             =item $departure->delay
335              
336             Expected delay from scheduled departure time in minutes. A delay of 0
337             indicates departure on time. undef when no realtime information is available.
338              
339             =item $departure->destination
340              
341             Destination name.
342              
343             =item $departure->hints
344              
345             Additional information related to the departure (list of strings). If
346             departures for an address were requested, this is the stop name, otherwise it
347             may be recent news related to the line's schedule.
348              
349             =item $departure->id
350              
351             Stringified unique(?) identifier of this departure; suitable for passing to
352             Travel::Status::DE::EFA->new(stopseq) after decomposing it again.
353             The returned string combines B<stateless>, B<stop_id_num> (or the ID of the first
354             stop in B<route_pre>, if present), B<sched_datetime>, and B<key>.
355              
356             =item $departure->is_cancelled
357              
358             1 if the departure got cancelled, 0 otherwise.
359              
360             =item $departure->key
361              
362             Key of this departure of the corresponding line. Unique for a given day when
363             combined with B<stateless>.
364              
365             =item $departure->line
366              
367             The name/number of the line.
368              
369             =item $departure->lineref
370              
371             Travel::Status::DE::EFA::Line(3pm) object describing the departing line in
372             detail.
373              
374             =item $departure->mot
375              
376             Returns the "mode of transport" number. This is usually an integer between 0
377             and 11.
378              
379             =item $departure->mot_name
380              
381             Returns the "mode of transport", for instance "zug", "s-bahn", "tram" or
382             "sonstige".
383              
384             =item $departure->occupancy
385              
386             Returns expected occupancy, if available, undef otherwise.
387              
388             Occupancy values are passed from the backend as-is. Known values are
389             "MANY_SEATS" (low occupation), "FEW_SEATS" (high occupation),
390             "STANDING_ONLY" (very high occupation), and "FULL" (boarding not advised).
391              
392             =item $departure->origin
393              
394             Origin name.
395              
396             =item $departure->platform
397              
398             Departure platform number (may not be a number).
399              
400             =item $departure->platform_db
401              
402             true if the platform number is operated by DB ("Gleis x"), false ("Bstg. x")
403             otherwise.
404              
405             Unfortunately, there is no distinction between tram and bus platforms yet,
406             which may also have the same numbers.
407              
408             =item $departure->route_interesting
409              
410             List of up to three "interesting" stations served by this departure. Is a
411             subset of B<route_post>. Each station is a Travel::Status::DE::EFA::Stop(3pm)
412             object.
413              
414             =item $departure->route_pre
415              
416             List of stations the vehicle passed (or will have passed) before this stop.
417             Each station is a Travel::Status::DE::EFA::Stop(3pm) object.
418              
419             =item $departure->route_post
420              
421             List of stations the vehicle will pass after this stop.
422             Each station is a Travel::Status::DE::EFA::Stop(3pm) object.
423              
424             =item $departure->rt_datetime
425              
426             DateTime(3pm) object holding the departure date and time according to
427             realtime data. Undef if unknown / unavailable.
428              
429             =item $departure->sched_datetime
430              
431             DateTime(3pm) object holding the scheduled departure date and time.
432              
433             =item $departure->stateless
434              
435             Unique line identifier.
436              
437             =item $departure->train_type
438              
439             Train type, e.g. "ICE". Typically only defined for long-distance trains.
440              
441             =item $departure->train_name
442              
443             Train name, e.g. "ICE International" or "InterCityExpresS" or "Deichgraf".
444             Typically only defined for long-distance trains.
445              
446             =item $departure->train_no
447              
448             Train number. Only defined if departure is a train.
449              
450             =item $departure->type
451              
452             Type of the departure. Note that efa.vrr.de sometimes puts bogus data in this
453             field. See L</DEPARTURE TYPES>.
454              
455             =back
456              
457             =head2 INTERNAL
458              
459             =over
460              
461             =item $departure = Travel::Status::DE::EFA::Departure->new(I<%data>)
462              
463             Returns a new Travel::Status::DE::EFA::Departure object. You should not need to
464             call this.
465              
466             =item $departure->TO_JSON
467              
468             Allows the object data to be serialized to JSON.
469              
470             =back
471              
472             =head1 DEPARTURE TYPES
473              
474             The following are known so far:
475              
476             =over
477              
478             =item * Abellio-Zug
479              
480             =item * Bus
481              
482             =item * Eurocity
483              
484             =item * Intercity-Express
485              
486             =item * NE (NachtExpress / night bus)
487              
488             =item * Niederflurbus
489              
490             =item * R-Bahn (RE / RegionalExpress)
491              
492             =item * S-Bahn
493              
494             =item * SB (Schnellbus)
495              
496             =item * StraE<szlig>enbahn
497              
498             =item * U-Bahn
499              
500             =back
501              
502             =head1 DIAGNOSTICS
503              
504             None.
505              
506             =head1 DEPENDENCIES
507              
508             =over
509              
510             =item Class::Accessor(3pm)
511              
512             =back
513              
514             =head1 BUGS AND LIMITATIONS
515              
516             C<< $result->type >> may contain bogus data. This comes from the efa.vrr.de
517             interface.
518              
519             =head1 SEE ALSO
520              
521             Travel::Status::DE::EFA(3pm).
522              
523             =head1 AUTHOR
524              
525             Copyright (C) 2011-2025 Birte Kristina Friesel E<lt>derf@finalrewind.orgE<gt>
526              
527             =head1 LICENSE
528              
529             This module is licensed under the same terms as Perl itself.