File Coverage

blib/lib/Travel/Status/DE/IRIS/Result.pm
Criterion Covered Total %
statement 310 392 79.0
branch 68 100 68.0
condition 59 117 50.4
subroutine 46 56 82.1
pod 25 43 58.1
total 508 708 71.7


line stmt bran cond sub pod time code
1             package Travel::Status::DE::IRIS::Result;
2              
3 7     7   59 use strict;
  7         21  
  7         235  
4 7     7   48 use warnings;
  7         24  
  7         195  
5 7     7   170 use 5.014;
  7         30  
6 7     7   43 use utf8;
  7         19  
  7         64  
7              
8 7     7   297 no if $] >= 5.018, warnings => 'experimental::smartmatch';
  7         16  
  7         79  
9              
10 7     7   589 use parent 'Class::Accessor';
  7         19  
  7         93  
11 7     7   18699 use Carp qw(cluck);
  7         16  
  7         456  
12 7     7   65 use DateTime;
  7         24  
  7         254  
13 7     7   39 use DateTime::Format::Strptime;
  7         17  
  7         99  
14 7     7   6999 use List::Compare;
  7         163829  
  7         348  
15 7     7   82 use List::MoreUtils qw(none uniq lastval);
  7         86  
  7         88  
16 7     7   6457 use Scalar::Util qw(weaken);
  7         17  
  7         37779  
17              
18             our $VERSION = '1.88';
19              
20             Travel::Status::DE::IRIS::Result->mk_ro_accessors(
21             qw(arrival arrival_delay arrival_has_realtime arrival_is_additional arrival_is_cancelled arrival_hidden
22             date datetime delay
23             departure departure_delay departure_has_realtime departure_is_additional departure_is_cancelled departure_hidden
24             ds100 has_realtime is_transfer is_unscheduled is_wing
25             line_no old_train_id old_train_no operator platform raw_id
26             realtime_xml route_start route_end
27             sched_arrival sched_departure sched_platform sched_route_start
28             sched_route_end start
29             station station_uic
30             stop_no time train_id train_no transfer type
31             unknown_t unknown_o wing_id wing_of)
32             );
33              
34             # {{{ Data (message codes, station fixups)
35              
36             my %translation = (
37             1 => 'Nähere Informationen in Kürze',
38             2 => 'Polizeieinsatz',
39             3 => 'Feuerwehreinsatz auf der Strecke',
40             4 => 'Kurzfristiger Personalausfall', # xlsx: missing
41             5 => 'Ärztliche Versorgung eines Fahrgastes',
42             6 => 'Betätigen der Notbremse', # xlsx: "Unbefugtes Ziehen der Notbremse"
43             7 => 'Unbefugte Personen auf der Strecke',
44             8 => 'Notarzteinsatz auf der Strecke',
45             9 => 'Streikauswirkungen',
46             10 => 'Tiere auf der Strecke',
47             11 => 'Unwetter',
48             12 => 'Warten auf ein verspätetes Schiff',
49             13 => 'Pass- und Zollkontrolle',
50             14 => 'Defekt am Bahnhof', # xlsx: "Technischer Defekt am Bahnhof"
51             15 => 'Beeinträchtigung durch Vandalismus',
52             16 => 'Entschärfung einer Fliegerbombe',
53             17 => 'Beschädigung einer Brücke',
54             18 => 'Umgestürzter Baum auf der Strecke',
55             19 => 'Unfall an einem Bahnübergang',
56             20 => 'Tiere im Gleis', # xlsx: missing
57             21 => 'Warten auf Anschlussreisende',
58             22 => 'Witterungsbedingte Beeinträchtigung',
59             23 => 'Feuerwehreinsatz auf Bahngelände', # xlsx: missing
60             24 => 'Verspätung im Ausland',
61             25 => 'Bereitstellung weiterer Wagen',
62             26 => 'Abhängen von Wagen',
63             28 => 'Gegenstände auf der Strecke',
64             29 => 'Ersatzverkehr mit Bus ist eingerichtet',
65             31 => 'Bauarbeiten',
66             32 => 'Verzögerung beim Ein-/Ausstieg'
67             , # xlsx: "Unterstützung beim Ein- und Ausstieg"
68             33 => 'Defekt an der Oberleitung', # xlsx: "Reparatur an der Oberleitung"
69             34 => 'Defekt an einem Signal', # xlsx: "Reparatur an einem Signal"
70             35 => 'Streckensperrung',
71             36 => 'Defekt am Zug', # xlsx: "Reparatur am Zug"
72             37 => 'Defekt am Wagen', # xlsx: missing
73             38 => 'Defekt an der Strecke', # xlsx: "Reparatur an der Strecke"
74             39 => 'Anhängen von zusätzlichen Wagen', # xlsx: missing
75             40 => 'Defektes Stellwerk',
76             41 => 'Defekt an einem Bahnübergang'
77             , # xlsx: "Technischer Defekt an einem Bahnüburgang"
78             42 => 'Außerplanmäßige Geschwindigkeitsbeschränkung'
79             , # xlsx: "Vorübergehend verminderte Geschwindigkeit auf der Strecke"
80             43 => 'Verspätung eines vorausfahrenden Zuges',
81             44 => 'Warten auf einen entgegenkommenden Zug',
82              
83             # TODO for Oct 2021: switch 45, 46 to "Vorfahrt eines anderen Zuges"
84             45 => 'Überholung durch anderen Zug', # xlsx: "Vorfahrt eines anderen Zuges"
85             46 => 'Warten auf freie Einfahrt', # xlsx: "Vorfahrt eines anderen Zuges"
86              
87             47 =>
88             'Verspätete Bereitstellung', # xlsx: "Verspätete Bereitstellung des Zuges"
89             48 => 'Verspätung aus vorheriger Fahrt',
90             49 => 'Kurzfristiger Personalausfall',
91             50 => 'Kurzfristige Erkrankung von Personal',
92             51 => 'Verspätetes Personal aus vorheriger Fahrt',
93             52 => 'Streik',
94             53 => 'Unwetterauswirkungen',
95             54 => 'Verfügbarkeit der Gleise derzeit eingeschränkt',
96             55 => 'Defekt an einem anderen Zug',
97             56 => 'Warten auf Anschlussreisende', # aus einem Bus
98             57 => 'Zusätzlicher Halt', # xslx: "Zusätzlicher Halt zum Ein- und Ausstieg"
99             58 => 'Umleitung', # xlsx: "Umleitung des Zuges"
100             59 => 'Schnee und Eis',
101             60 => 'Witterungsbedingt verminderte Geschwindigkeit',
102             61 => 'Defekte Tür',
103             62 => 'Behobener Defekt am Zug', # r 36
104             63 => 'Technische Untersuchung am Zug',
105             64 => 'Defekt an einer Weiche', # xlsx: "Reparatur an der Weiche"
106             65 => 'Erdrutsch',
107             66 => 'Hochwasser',
108             67 => 'Behördliche Maßnahme',
109             68 => 'Hohes Fahrgastaufkommen'
110             , # xlsx: "Hohes Fahrgastaufkommen verlängert Ein- und Ausstieg"
111             69 => 'Zug verkehrt mit verminderter Geschwindigeit',
112             70 => 'WLAN nicht verfügbar',
113             71 => 'WLAN in einzelnen Wagen nicht verfügbar',
114             72 => 'Info/Entertainment nicht verfügbar',
115             73 => 'Heute: Mehrzweckabteil vorne', # r 74
116             74 => 'Heute: Mehrzweckabteil hinten', # r 73
117             75 => 'Heute: 1. Klasse vorne', # r 76
118             76 => 'Heute: 1. Klasse hinten', # r 75
119             77 => '1. Klasse fehlt',
120             78 => 'Ersatzverkehr mit Bus ist eingerichtet',
121             79 => 'Mehrzweckabteil fehlt',
122             80 => 'Abweichende Wagenreihung',
123             81 => 'Fahrzeugtausch',
124             82 => 'Mehrere Wagen fehlen',
125             83 => 'Defekte fahrzeuggebundene Einstiegshilfe',
126             84 => 'Zug verkehrt richtig gereiht', # r 80 82 85
127             85 => 'Ein Wagen fehlt',
128             86 => 'Gesamter Zug ohne Reservierung',
129             87 => 'Einzelne Wagen ohne Reservierung',
130             88 => 'Keine Qualitätsmängel', # r 80 82 83 85 86 87 90 91 92 93 96 97 98
131             89 => 'Reservierungen sind wieder vorhanden', # -> 86 87
132             90 => 'Kein gastronomisches Angebot',
133             91 => 'Fahrradmitnahme nicht möglich',
134             92 => 'Eingeschränkte Fahrradbeförderung',
135             93 => 'Behindertengerechte Einrichtung fehlt',
136             94 => 'Ersatzbewirtschaftung',
137             95 => 'Universal-WC fehlt',
138             96 => 'Der Zug ist stark überbesetzt', # r 97
139             97 => 'Der Zug ist überbesetzt', # r 96
140             98 => 'Sonstige Qualitätsmängel',
141             99 => 'Verzögerungen im Betriebsablauf',
142              
143             # Occasionally, there's a message with ID 900. In all cases observed so far,
144             # it was used for "Anschlussbus wartet". However, as we don't know which bus
145             # it refers to, we don't show it to users.
146             );
147              
148             # IRIS may return "Betriebsstelle nicht bekannt" for some recently added
149             # stations. Fix those manually.
150             my %fixup = (
151             8002795 => 'Herten(Westf)',
152             8003983 => 'Merklingen - Schwäbische Alb',
153             8005493 => 'Schwetzingen-Hirschacker',
154             8070678 => 'Metzingen-Neuhausen',
155             );
156              
157             # }}}
158             # {{{ Constructor
159              
160             sub new {
161 1140     1140 1 9730 my ( $obj, %opt ) = @_;
162              
163 1140         2502 my $ref = \%opt;
164              
165 1140         9069 my ( $train_id, $start_ts, $stop_no ) = split( /.\K-/, $opt{raw_id} );
166              
167 1140         2762 bless( $ref, $obj );
168              
169 1140   33     3122 $ref->{strptime_obj} //= DateTime::Format::Strptime->new(
170             pattern => '%y%m%d%H%M',
171             time_zone => 'Europe/Berlin',
172             );
173              
174 1140         4051 $ref->{wing_id} = "${train_id}-${start_ts}";
175 1140         2329 $ref->{is_wing} = 0;
176 1140         3741 $train_id =~ s{^-}{};
177              
178 1140         2920 $ref->{start} = $ref->parse_ts($start_ts);
179              
180 1140         1037896 $ref->{train_id} = $train_id;
181 1140         2589 $ref->{stop_no} = $stop_no;
182              
183 1140 100       2950 if ( $opt{transfer} ) {
184 24         189 my ($transfer) = split( /.\K-/, $opt{transfer} );
185 24         120 $transfer =~ s{^-}{};
186 24         60 $ref->{transfer} = $transfer;
187             }
188              
189             my $ar = $ref->{arrival} = $ref->{sched_arrival}
190 1140         2943 = $ref->parse_ts( $opt{arrival_ts} );
191             my $dp = $ref->{departure} = $ref->{sched_departure}
192 1140         871441 = $ref->parse_ts( $opt{departure_ts} );
193              
194 1140 50 66     832558 if ( not( defined $ar or defined $dp ) ) {
195             cluck(
196             sprintf(
197             "Neither arrival '%s' nor departure '%s' are valid "
198             . "timestamps - can't handle this train",
199             $opt{arrival_ts}, $opt{departure_ts}
200             )
201 0         0 );
202             }
203              
204 1140   66     4039 my $dt = $ref->{datetime} = $dp // $ar;
205              
206 1140         6511 $ref->{date} = $dt->strftime('%d.%m.%Y');
207 1140         65334 $ref->{time} = $dt->strftime('%H:%M');
208 1140         40158 $ref->{epoch} = $dt->epoch;
209              
210             $ref->{route_pre} = $ref->{sched_route_pre}
211 1140   100     23290 = [ split( qr{[|]}, $ref->{route_pre} // q{} ) ];
212             $ref->{route_post} = $ref->{sched_route_post}
213 1140   100     13804 = [ split( qr{[|]}, $ref->{route_post} // q{} ) ];
214              
215 1140         4806 $ref->fixup_route( $ref->{route_pre} );
216 1140         3128 $ref->fixup_route( $ref->{route_post} );
217              
218 1140 100       3074 $ref->{route_pre_incomplete} = $ref->{route_end} ? 1 : 0;
219 1140 50       2780 $ref->{route_post_incomplete} = $ref->{route_post} ? 1 : 0;
220              
221 1140         2390 $ref->{sched_platform} = $ref->{platform};
222             $ref->{route_end}
223             = $ref->{sched_route_end}
224             = $ref->{route_end}
225             || $ref->{route_post}[-1]
226 1140   66     5771 || $ref->{station};
227             $ref->{route_start}
228             = $ref->{sched_route_start}
229             = $ref->{route_start}
230             || $ref->{route_pre}[0]
231 1140   66     4842 || $ref->{station};
232              
233 1140         4045 return $ref;
234             }
235              
236             # }}}
237             # {{{ Internal Helpers
238              
239             sub fixup_route {
240 3012     3012 0 5597 my ( $self, $route ) = @_;
241 3012         4315 for my $stop ( @{$route} ) {
  3012         6447  
242 27180 50       48854 if ( $stop =~ m{^Betriebsstelle nicht bekannt (\d+)$} ) {
243 0 0       0 if ( $fixup{$1} ) {
244 0         0 $stop = $fixup{$1};
245             }
246             }
247             }
248             }
249              
250             sub parse_ts {
251 5011     5011 0 10637 my ( $self, $string ) = @_;
252              
253 5011 100       11195 if ( defined $string ) {
254 4655         14572 return $self->{strptime_obj}->parse_datetime($string);
255             }
256 356         882 return;
257             }
258              
259             # List::Compare does not keep the order of its arguments (even with unsorted).
260             # So we need to re-sort all stops to maintain their original order.
261             sub sorted_sublist {
262 0     0 0 0 my ( $self, $list, $sublist ) = @_;
263 0         0 my %pos;
264              
265 0 0 0     0 if ( not $sublist or not @{$sublist} ) {
  0         0  
266 0         0 return;
267             }
268              
269 0         0 for my $i ( 0 .. $#{$list} ) {
  0         0  
270 0         0 $pos{ $list->[$i] } = $i;
271             }
272              
273 0         0 my @sorted = sort { $pos{$a} <=> $pos{$b} } @{$sublist};
  0         0  
  0         0  
274              
275 0         0 return @sorted;
276             }
277              
278             sub superseded_messages {
279 3     3 0 8 my ( $self, $msg ) = @_;
280 3         29 my %superseded = (
281             62 => [36],
282             73 => [74],
283             74 => [73],
284             75 => [76],
285             76 => [75],
286             84 => [ 80, 82, 85 ],
287             88 => [ 80, 82, 83, 85, 86, 87, 90, 91, 92, 93, 96, 97, 98 ],
288             89 => [ 86, 87 ],
289             96 => [97],
290             97 => [96],
291             );
292              
293 3   50     6 return @{ $superseded{$msg} // [] };
  3         31  
294             }
295              
296             # }}}
297             # {{{ Internal Setters for IRIS.pm
298              
299             sub set_ar {
300 760     760 0 39871 my ( $self, %attrib ) = @_;
301              
302 760 100 100     3355 if ( $attrib{status} and $attrib{status} eq 'c' ) {
    50 66        
303 54         231 $self->{has_realtime} = $self->{arrival_has_realtime} = 1;
304 54         150 $self->{arrival_is_cancelled} = 1;
305             }
306             elsif ( $attrib{status} and $attrib{status} eq 'a' ) {
307 0         0 $self->{arrival_is_additional} = 1;
308             }
309             else {
310 706         1924 $self->{arrival_is_additional} = 0;
311 706         1621 $self->{arrival_is_cancelled} = 0;
312             }
313              
314 760 50       1540 if ( $attrib{arrival_hidden} ) {
315 0         0 $self->{arrival_hidden} = $attrib{arrival_hidden};
316             }
317              
318             # unscheduled arrivals may not appear in the plan, but we do need to
319             # know their planned arrival time
320 760 100       1536 if ( $attrib{plan_arrival_ts} ) {
321             $self->{sched_arrival}
322 184         537 = $self->parse_ts( $attrib{plan_arrival_ts} );
323             }
324              
325 760 100       169246 if ( $attrib{arrival_ts} ) {
326 648         1905 $self->{has_realtime} = $self->{arrival_has_realtime} = 1;
327 648         1756 $self->{arrival} = $self->parse_ts( $attrib{arrival_ts} );
328 648 100       588552 if ( not $self->{arrival_is_cancelled} ) {
329             $self->{delay} = $self->{arrival_delay}
330 640         2146 = $self->arrival->subtract_datetime( $self->sched_arrival )
331             ->in_units('minutes');
332             }
333             }
334             else {
335 112         514 $self->{arrival} = $self->{sched_arrival};
336 112   50     568 $self->{arrival_delay} //= 0;
337 112   50     416 $self->{delay} //= 0;
338             }
339              
340 760 100       326339 if ( $attrib{platform} ) {
341 14         98 $self->{platform} = $attrib{platform};
342             }
343             else {
344 746         3396 $self->{platform} = $self->{sched_platform};
345             }
346              
347 760 100       1635 if ( defined $attrib{route_pre} ) {
348 190   50     2614 $self->{route_pre} = [ split( qr{[|]}, $attrib{route_pre} // q{} ) ];
349 190         908 $self->fixup_route( $self->{route_pre} );
350 190 100       333 if ( @{ $self->{route_pre} } ) {
  190         599  
351 144         590 $self->{route_start} = $self->{route_pre}[0];
352             }
353             }
354             else {
355 570         1569 $self->{route_pre} = $self->{sched_route_pre};
356 570         1564 $self->{route_start} = $self->{sched_route_start};
357             }
358              
359             # also only for unscheduled arrivals
360 760 100       1760 if ( $attrib{sched_route_pre} ) {
361             $self->{sched_route_pre}
362 184   50     2567 = [ split( qr{[|]}, $attrib{sched_route_pre} // q{} ) ];
363 184         853 $self->fixup_route( $self->{sched_route_pre} );
364 184         474 $self->{sched_route_start} = $self->{sched_route_pre}[0];
365             }
366              
367 760         2641 return $self;
368             }
369              
370             sub set_dp {
371 686     686 0 34097 my ( $self, %attrib ) = @_;
372              
373 686 100 100     2974 if ( $attrib{status} and $attrib{status} eq 'c' ) {
    50 66        
374 53         164 $self->{has_realtime} = $self->{arrival_has_realtime} = 1;
375 53         116 $self->{departure_is_cancelled} = 1;
376             }
377             elsif ( $attrib{status} and $attrib{status} eq 'a' ) {
378 0         0 $self->{departure_is_additional} = 1;
379             }
380             else {
381 633         1323 $self->{departure_is_additional} = 0;
382 633         1255 $self->{departure_is_cancelled} = 0;
383             }
384              
385 686 50       1381 if ( $attrib{departure_hidden} ) {
386 0         0 $self->{departure_hidden} = $attrib{departure_hidden};
387             }
388              
389             # unscheduled arrivals may not appear in the plan, but we do need to
390             # know their planned arrival time
391 686 100       1373 if ( $attrib{plan_departure_ts} ) {
392             $self->{sched_departure}
393 176         550 = $self->parse_ts( $attrib{plan_departure_ts} );
394             }
395              
396 686 100       158794 if ( $attrib{departure_ts} ) {
397 572         1225 $self->{has_realtime} = $self->{departure_has_realtime} = 1;
398 572         1587 $self->{departure} = $self->parse_ts( $attrib{departure_ts} );
399 572 100       509803 if ( not $self->{departure_is_cancelled} ) {
400             $self->{delay} = $self->{departure_delay}
401 564         1944 = $self->departure->subtract_datetime( $self->sched_departure )
402             ->in_units('minutes');
403             }
404             }
405             else {
406 114         463 $self->{departure} = $self->{sched_departure};
407 114   100     326 $self->{delay} //= 0;
408 114   50     446 $self->{departure_delay} //= 0;
409             }
410              
411 686 100       284249 if ( $attrib{platform} ) {
412 7         57 $self->{platform} = $attrib{platform};
413             }
414             else {
415 679         1690 $self->{platform} = $self->{sched_platform};
416             }
417              
418 686 100       1472 if ( defined $attrib{route_post} ) {
419 182   50     2247 $self->{route_post} = [ split( qr{[|]}, $attrib{route_post} // q{} ) ];
420 182         801 $self->fixup_route( $self->{route_post} );
421 182 100       292 if ( @{ $self->{route_post} } ) {
  182         529  
422 137         481 $self->{route_end} = $self->{route_post}[-1];
423             }
424             }
425             else {
426 504         1426 $self->{route_post} = $self->{sched_route_post};
427 504         1513 $self->{route_end} = $self->{sched_route_end};
428             }
429              
430             # also only for unscheduled departures
431 686 100       1701 if ( $attrib{sched_route_post} ) {
432             $self->{sched_route_post}
433 176   50     2367 = [ split( qr{[|]}, $attrib{sched_route_post} // q{} ) ];
434 176         855 $self->fixup_route( $self->{sched_route_post} );
435 176         444 $self->{sched_route_end} = $self->{sched_route_post}[-1];
436             }
437              
438 686         4099 return $self;
439             }
440              
441             sub set_messages {
442 827     827 0 2606 my ( $self, %messages ) = @_;
443              
444 827         2432 $self->{messages} = \%messages;
445              
446 827         1837 return $self;
447             }
448              
449             sub set_realtime {
450 827     827 0 1667 my ( $self, $xmlobj ) = @_;
451              
452 827         2203 $self->{realtime_xml} = $xmlobj;
453              
454 827         1641 return $self;
455             }
456              
457             sub add_raw_ref {
458 1     1 0 45 my ( $self, %attrib ) = @_;
459              
460 1         2 push( @{ $self->{refs} }, \%attrib );
  1         5  
461              
462 1         4 return $self;
463             }
464              
465             sub set_unscheduled {
466 121     121 0 302 my ( $self, $unscheduled ) = @_;
467              
468 121         348 $self->{is_unscheduled} = $unscheduled;
469             }
470              
471             sub add_arrival_wingref {
472 14     14 0 45 my ( $self, $ref ) = @_;
473              
474 14         42 my $backref = $self;
475              
476 14         79 weaken($ref);
477 14         63 weaken($backref);
478 14         38 $ref->{is_wing} = 1;
479 14         51 $ref->{wing_of} = $self;
480 14         30 push( @{ $self->{arrival_wings} }, $ref );
  14         61  
481 14         57 return $self;
482             }
483              
484             sub add_departure_wingref {
485 8     8 0 47 my ( $self, $ref ) = @_;
486              
487 8         20 my $backref = $self;
488              
489 8         49 weaken($ref);
490 8         66 weaken($backref);
491 8         22 $ref->{is_wing} = 1;
492 8         20 $ref->{wing_of} = $self;
493 8         16 push( @{ $self->{departure_wings} }, $ref );
  8         29  
494 8         28 return $self;
495             }
496              
497             sub add_reference {
498 0     0 0 0 my ( $self, $ref ) = @_;
499              
500 0         0 $ref->add_inverse_reference($self);
501 0         0 weaken($ref);
502 0         0 push( @{ $self->{replacement_for} }, $ref );
  0         0  
503 0         0 return $self;
504             }
505              
506             sub merge_with_departure {
507 10     10 0 24 my ( $self, $result ) = @_;
508              
509             # result must be departure-only
510              
511 10         20 $self->{is_transfer} = 1;
512              
513 10         59 $self->{old_train_id} = $self->{train_id};
514 10         31 $self->{old_train_no} = $self->{train_no};
515              
516             # departure is preferred over arrival, so overwrite default values
517 10         38 $self->{date} = $result->{date};
518 10         33 $self->{time} = $result->{time};
519 10         19 $self->{epoch} = $result->{epoch};
520 10         22 $self->{datetime} = $result->{datetime};
521 10         17 $self->{train_id} = $result->{train_id};
522 10         24 $self->{train_no} = $result->{train_no};
523              
524 10         17 $self->{departure} = $result->{departure};
525 10         40 $self->{departure_wings} = $result->{departure_wings};
526 10         22 $self->{route_end} = $result->{route_end};
527 10         20 $self->{route_post} = $result->{route_post};
528 10         18 $self->{sched_departure} = $result->{sched_departure};
529 10         24 $self->{sched_route_post} = $result->{sched_route_post};
530              
531             # update realtime info only if applicable
532 10   33     42 $self->{is_cancelled} ||= $result->{is_cancelled};
533              
534 10         27 return $self;
535             }
536              
537             sub add_inverse_reference {
538 0     0 0 0 my ( $self, $ref ) = @_;
539              
540 0         0 weaken($ref);
541 0         0 push( @{ $self->{replaced_by} }, $ref );
  0         0  
542 0         0 return $self;
543             }
544              
545             # }}}
546             # {{{ Public Accessors
547              
548             sub is_additional {
549 0     0 1 0 my ($self) = @_;
550              
551 0 0 0     0 if ( $self->{arrival_is_additional} and $self->{departure_is_additional} ) {
552 0         0 return 1;
553             }
554 0 0 0     0 if ( $self->{arrival_is_additional}
555             and not defined $self->{departure_is_additional} )
556             {
557 0         0 return 1;
558             }
559 0 0 0     0 if ( not defined $self->{arrival_is_additional}
560             and $self->{departure_is_additional} )
561             {
562 0         0 return 1;
563             }
564 0         0 return 0;
565             }
566              
567             sub is_cancelled {
568 2     2 1 2677 my ($self) = @_;
569              
570 2 50 66     21 if ( $self->{arrival_is_cancelled} and $self->{departure_is_cancelled} ) {
571 1         6 return 1;
572             }
573 1 50 33     6 if ( $self->{arrival_is_cancelled}
574             and not defined $self->{departure_is_cancelled} )
575             {
576 0         0 return 1;
577             }
578 1 0 33     4 if ( not defined $self->{arrival_is_cancelled}
579             and $self->{departure_is_cancelled} )
580             {
581 0         0 return 1;
582             }
583 1         9 return 0;
584             }
585              
586             sub additional_stops {
587 0     0 1 0 my ($self) = @_;
588              
589             $self->{comparator} //= List::Compare->new(
590             {
591 0   0     0 lists => [ $self->{sched_route_post}, $self->{route_post} ],
592             unsorted => 1,
593             }
594             );
595              
596             return $self->sorted_sublist( $self->{route_post},
597 0         0 [ $self->{comparator}->get_complement ] );
598             }
599              
600             sub canceled_stops {
601 0     0 1 0 my ($self) = @_;
602              
603             $self->{comparator} //= List::Compare->new(
604             {
605 0   0     0 lists => [ $self->{sched_route_post}, $self->{route_post} ],
606             unsorted => 1,
607             }
608             );
609              
610             return $self->sorted_sublist( $self->{sched_route_post},
611 0         0 [ $self->{comparator}->get_unique ] );
612             }
613              
614             sub classes {
615 1     1 1 4235 my ($self) = @_;
616              
617 1   50     9 my @classes = split( //, $self->{classes} // q{} );
618              
619 1         7 return @classes;
620             }
621              
622             sub origin {
623 134     134 1 86557 my ($self) = @_;
624              
625 134         390 return $self->route_start;
626             }
627              
628             sub destination {
629 134     134 1 90891 my ($self) = @_;
630              
631 134         396 return $self->route_end;
632             }
633              
634             sub delay_messages {
635 1     1 1 5 my ($self) = @_;
636              
637 1         3 my @keys = sort keys %{ $self->{messages} };
  1         12  
638 1         4 my @msgs = grep { $_->[1] eq 'd' } map { $self->{messages}{$_} } @keys;
  8         19  
  8         18  
639 1         3 my @msgids = uniq( map { $_->[2] } @msgs );
  7         18  
640 1         4 my @ret;
641              
642 1         4 for my $id (@msgids) {
643 2 50       6 if ( my @superseded = $self->superseded_messages($id) ) {
644 0         0 @ret = grep { not( $_->[2] ~~ \@superseded ) } @ret;
  0         0  
645             }
646 2     3   20 my $msg = lastval { $_->[2] == $id } @msgs;
  3         7  
647 2         10 push( @ret, $msg );
648             }
649              
650             @ret = reverse
651 1         3 map { [ $self->parse_ts( $_->[0] ), $self->translate_msg( $_->[2] ) ] }
  2         7  
652             @ret;
653              
654 1         13 return @ret;
655             }
656              
657             sub arrival_wings {
658 4     4 1 5189 my ($self) = @_;
659              
660 4 100       14 if ( $self->{arrival_wings} ) {
661 2         4 return @{ $self->{arrival_wings} };
  2         12  
662             }
663 2         14 return;
664             }
665              
666             sub departure_wings {
667 6     6 1 17 my ($self) = @_;
668              
669 6 100       20 if ( $self->{departure_wings} ) {
670 4         6 return @{ $self->{departure_wings} };
  4         30  
671             }
672 2         10 return;
673             }
674              
675             sub replaced_by {
676 0     0 1 0 my ($self) = @_;
677              
678 0 0       0 if ( $self->{replaced_by} ) {
679 0         0 return @{ $self->{replaced_by} };
  0         0  
680             }
681 0         0 return;
682             }
683              
684             sub replacement_for {
685 0     0 1 0 my ($self) = @_;
686              
687 0 0       0 if ( $self->{replacement_for} ) {
688 0         0 return @{ $self->{replacement_for} };
  0         0  
689             }
690 0         0 return;
691             }
692              
693             sub qos_messages {
694 1     1 1 4 my ($self) = @_;
695              
696 1         3 my @keys = sort keys %{ $self->{messages} };
  1         59  
697             my @msgs
698 1         9 = grep { $_->[1] ~~ [qw[f q]] } map { $self->{messages}{$_} } @keys;
  8         29  
  8         19  
699 1         5 my @ret;
700              
701 1         3 for my $msg (@msgs) {
702 1 50       8 if ( my @superseded = $self->superseded_messages( $msg->[2] ) ) {
703 0         0 @ret = grep { not( $_->[2] ~~ \@superseded ) } @ret;
  0         0  
704             }
705 1         3 @ret = grep { $_->[2] != $msg->[2] } @ret;
  0         0  
706              
707             # 88 is "no qos shortcomings" and only required to cancel previous qos
708             # messages. Same for 84 ("correct wagon order") and 89 ("reservations
709             # display is working again").
710 1 50 33     13 if ( $msg->[2] != 84 and $msg->[2] != 88 and $msg->[2] != 89 ) {
      33        
711 1         5 push( @ret, $msg );
712             }
713             }
714              
715             @ret
716 1         2 = map { [ $self->parse_ts( $_->[0] ), $self->translate_msg( $_->[2] ) ] }
  1         4  
717             reverse @ret;
718              
719 1         9 return @ret;
720             }
721              
722             sub raw_messages {
723 0     0 0 0 my ($self) = @_;
724              
725 0         0 my @messages = reverse sort keys %{ $self->{messages} };
  0         0  
726             my @ret = map {
727 0         0 [
728             $self->parse_ts( $self->{messages}->{$_}->[0] ),
729 0         0 $self->{messages}->{$_}->[2]
730             ]
731             } @messages;
732              
733 0         0 return @ret;
734             }
735              
736             sub messages {
737 1     1 1 67 my ($self) = @_;
738              
739 1         3 my @messages = reverse sort keys %{ $self->{messages} };
  1         12  
740             my @ret = map {
741 1         4 [
742             $self->parse_ts( $self->{messages}->{$_}->[0] ),
743 8         26 $self->translate_msg( $self->{messages}->{$_}->[2] )
744             ]
745             } @messages;
746              
747 1         16 return @ret;
748             }
749              
750             sub info {
751 1     1 1 15 my ($self) = @_;
752              
753 1         3 my @messages = sort keys %{ $self->{messages} };
  1         14  
754 1         5 my @ids = uniq( map { $self->{messages}{$_}->[2] } @messages );
  8         35  
755              
756 1         6 my @info = map { $self->translate_msg($_) } @ids;
  3         12  
757              
758 1         13 return @info;
759             }
760              
761             sub line {
762 268     268 1 472 my ($self) = @_;
763              
764             return sprintf( '%s %s',
765             $self->{type} // 'Zug',
766 268   50     2823 $self->{line_no} // $self->{train_no} // '-' );
      33        
      50        
767             }
768              
769             sub route_pre {
770 4     4 1 11 my ($self) = @_;
771              
772 4         7 return @{ $self->{route_pre} };
  4         26  
773             }
774              
775             sub route_post {
776 8     8 1 20 my ($self) = @_;
777              
778 8         12 return @{ $self->{route_post} };
  8         58  
779             }
780              
781             sub route {
782 2     2 1 19 my ($self) = @_;
783              
784 2         9 return ( $self->route_pre, $self->{station}, $self->route_post );
785             }
786              
787             sub train {
788 134     134 1 93236 my ($self) = @_;
789              
790 134         326 return $self->line;
791             }
792              
793             sub route_interesting {
794 4     4 1 11 my ( $self, $max_parts ) = @_;
795              
796 4         10 my @via = $self->route_post;
797 4         9 my ( @via_main, @via_show, $last_stop );
798 4   50     23 $max_parts //= 3;
799              
800             # Centraal: dutch main station (Hbf in .nl)
801             # HB: swiss main station (Hbf in .ch)
802             # hl.n.: czech main station (Hbf in .cz)
803 4         8 for my $stop (@via) {
804 16 100       69 if ( $stop =~ m{ HB $ | hl\.n\. $ | Hbf | Centraal | Flughafen }x ) {
805 9         19 push( @via_main, $stop );
806             }
807             }
808             $last_stop
809 4 50       17 = $self->{route_post_incomplete} ? $self->{route_end} : pop(@via);
810              
811 4 100 100     20 if ( @via_main and $via_main[-1] eq $last_stop ) {
812 2         4 pop(@via_main);
813             }
814 4 100 66     19 if ( @via and $via[-1] eq $last_stop ) {
815 3         5 pop(@via);
816             }
817              
818 4 100 66     19 if ( @via_main and @via and $via[0] eq $via_main[0] ) {
      100        
819 1         2 shift(@via_main);
820             }
821              
822 4 100       9 if ( @via < $max_parts ) {
823 2         4 @via_show = @via;
824             }
825             else {
826 2 100       6 if ( @via_main >= $max_parts ) {
827 1         3 @via_show = ( $via[0] );
828             }
829             else {
830 1         4 @via_show = splice( @via, 0, $max_parts - @via_main );
831             }
832              
833 2   66     10 while ( @via_show < $max_parts and @via_main ) {
834 4         7 my $stop = shift(@via_main);
835 4 50 33     21 if ( $stop ~~ \@via_show or $stop eq $last_stop ) {
836 0         0 next;
837             }
838 4         14 push( @via_show, $stop );
839             }
840             }
841              
842 4         10 for (@via_show) {
843 6         36 s{ \s? Hbf .* }{}x;
844             }
845              
846 4         25 return @via_show;
847              
848             }
849              
850             sub sched_route_pre {
851 2     2 1 19 my ($self) = @_;
852              
853 2         75 return @{ $self->{sched_route_pre} };
  2         17  
854             }
855              
856             sub sched_route_post {
857 2     2 1 7 my ($self) = @_;
858              
859 2         3 return @{ $self->{sched_route_post} };
  2         25  
860             }
861              
862             sub sched_route {
863 1     1 1 7 my ($self) = @_;
864              
865             return ( $self->sched_route_pre, $self->{station},
866 1         7 $self->sched_route_post );
867             }
868              
869             sub translate_msg {
870 14     14 0 10189 my ( $self, $msg ) = @_;
871              
872 14   33     95 return $translation{$msg} // "?($msg)";
873             }
874              
875             sub TO_JSON {
876 0     0 0   my ($self) = @_;
877              
878 0           my %copy = %{$self};
  0            
879 0           delete $copy{arrival_wings};
880 0           delete $copy{departure_wings};
881 0           delete $copy{realtime_xml};
882 0           delete $copy{replaced_by};
883 0           delete $copy{replacement_for};
884 0           delete $copy{strptime_obj};
885 0           delete $copy{wing_of};
886              
887 0           for my $datetime_key (
888             qw(arrival departure sched_arrival sched_departure start datetime))
889             {
890 0 0         if ( defined $copy{$datetime_key} ) {
891 0           $copy{$datetime_key} = $copy{$datetime_key}->epoch;
892             }
893             }
894              
895 0           return {%copy};
896             }
897              
898             # }}}
899              
900             1;
901              
902             __END__
903              
904             =head1 NAME
905              
906             Travel::Status::DE::IRIS::Result - Information about a single
907             arrival/departure received by Travel::Status::DE::IRIS
908              
909             =head1 SYNOPSIS
910              
911             for my $result ($status->results) {
912             printf(
913             "At %s: %s to %s from platform %s\n",
914             $result->time,
915             $result->line,
916             $result->destination,
917             $result->platform,
918             );
919             }
920              
921             =head1 VERSION
922              
923             version 1.88
924              
925             =head1 DESCRIPTION
926              
927             Travel::Status::DE::IRIs::Result describes a single arrival/departure
928             as obtained by Travel::Status::DE::IRIS. It contains information about
929             the platform, time, route and more.
930              
931             =head1 METHODS
932              
933             =head2 ACCESSORS
934              
935             =over
936              
937             =item $result->additional_stops
938              
939             Returns served stops which are not part of the schedule. I.e., this is the
940             set of actual stops (B<route_post>) minus the set of scheduled stops
941             (B<sched_route_post>).
942              
943             =item $result->arrival
944              
945             DateTime(3pm) object for the arrival date and time. undef if the
946             train starts here. Contains realtime data if available.
947              
948             =item $result->arrival_delay
949              
950             Estimated arrival delay in minutes (integer number). undef if no realtime
951             data is available, the train starts at the specified station, or there is
952             no scheduled arrival time (e.g. due to diversions). May be negative.
953              
954             =item $result->arrival_has_realtime
955              
956             True if "arrival" is based on real-time data.
957              
958             =item $result->arrival_hidden
959              
960             True if arrival should not be displayed to customers.
961             This often indicates an entry-only stop near the beginning of a train's journey.
962              
963             =item $result->arrival_is_additional
964              
965             True if the arrival at this stop is an additional (unscheduled) event, i.e.,
966             if the train started its journey earlier than planned.
967              
968             =item $result->arrival_is_cancelled
969              
970             True if the arrival at this stop has been cancelled.
971              
972             =item $result->arrival_wings
973              
974             Returns a list of weakened references to Travel::Status::DE::IRIS::Result(3pm)
975             objects which are coupled to this train on arrival. Returns nothing (false /
976             empty list) otherwise.
977              
978             =item $result->canceled_stops
979              
980             Returns stops which are scheduled, but will not be served by this train.
981             I.e., this is the set of scheduled stops (B<sched_route_post>) minus the set of
982             actual stops (B<route_post>).
983              
984             =item $result->classes
985              
986             List of characters indicating the class(es) of this train, may be empty. This
987             is slighty related to B<type>, but more generic. At this time, the following
988             classes are known:
989              
990             D Non-DB train. Usually local transport
991             D,F Non-DB train, long distance transport
992             F "Fernverkehr", long-distance transport
993             N "Nahverkehr", local and regional transport
994             S S-Bahn, rather slow local/regional transport
995              
996             =item $result->date
997              
998             Scheduled departure date if available, arrival date otherwise (e.g. if the
999             train ends here). String in dd.mm.YYYY format. Does not contain realtime data.
1000              
1001             =item $result->datetime
1002              
1003             DateTime(3pm) object for departure if available, arrival otherwise. Does not
1004             contain realtime data.
1005              
1006             =item $result->delay
1007              
1008             Estimated delay in minutes (integer number). Defaults to the departure delay,
1009             except for trains which terminate at the specifed station. Similar to
1010             C<< $result->departure_delay // $result->arrival_delay >>. undef if
1011             no realtime data is available. May be negative.
1012              
1013             =item $result->delay_messages
1014              
1015             Get all delay messages entered for this train. Returns a list of [datetime,
1016             string] listrefs sorted by newest first. The datetime part is a DateTime(3pm)
1017             object corresponding to the point in time when the message was entered, the
1018             string is the message. If a delay reason was entered more than once, only its
1019             most recent record will be returned.
1020              
1021             =item $result->departure
1022              
1023             DateTime(3pm) object for the departure date and time. undef if the train ends
1024             here. Contains realtime data if available.
1025              
1026             =item $result->departure_delay
1027              
1028             Estimated departure delay in minutes (integer number). undef if no realtime
1029             data is available, the train terminates at the specified station, or there is
1030             no scheduled departure time (e.g. due to diversions). May be negative.
1031              
1032             =item $result->departure_has_realtime
1033              
1034             True if "departure" is based on real-time data.
1035              
1036             =item $result->departure_hidden
1037              
1038             True if departure should not be displayed to customers.
1039             This often indicates an exit-only stop near the end of a train's journey.
1040              
1041             =item $result->departure_is_additional
1042              
1043             True if the train's departure at this stop is unscheduled (additional), i.e.,
1044             the route has been extended past its scheduled terminal stop.
1045              
1046             =item $result->departure_is_cancelled
1047              
1048             True if the train's departure at this stop has been cancelled, i.e., the train
1049             terminates here and does not continue its scheduled journey.
1050              
1051             =item $result->departure_wings
1052              
1053             Returns a list of weakened references to Travel::Status::DE::IRIS::Result(3pm)
1054             objects which are coupled to this train on departure. Returns nothing (false /
1055             empty list) otherwise.
1056              
1057             =item $result->destination
1058              
1059             Alias for route_end.
1060              
1061             =item $result->has_realtime
1062              
1063             True if arrival or departure time are based on real-time data. Note that this
1064             is different from C<< defined($esult->delay) >>. If delay is defined, some kind
1065             of realtime information for the train is available, but not necessarily its
1066             arrival/departure time. If has_realtime is true, arrival/departure time are
1067             available. This behaviour may change in the future.
1068              
1069             =item $result->info
1070              
1071             List of information strings. Contains both reasons for delays (which may or
1072             may not be up-to-date) and generic information such as missing carriages or
1073             broken toilets.
1074              
1075             =item $result->is_additional
1076              
1077             True if the train's arrival and departure at the stop are unscheduled
1078             additional stops, false otherwise.
1079              
1080             =item $result->is_cancelled
1081              
1082             True if the train was cancelled, false otherwise. Note that this does not
1083             contain information about replacement trains or route diversions.
1084              
1085             =item $result->is_transfer
1086              
1087             True if the train changes its ID at the current station, false otherwise.
1088              
1089             An ID change means: There are two results in the system (e.g. RE 10228
1090             ME<uuml>nster -> Duisburg, RE 30028 Duisburg -> DE<uuml>sseldorf), but they are
1091             the same train (RE line 2 from ME<uuml>nster to DE<uuml>sseldorf in this case)
1092             and should be treated as such. In this case, Travel::Status::DE::IRIS merges
1093             the results and indicates it by setting B<is_transfer> to a true value.
1094              
1095             In case of a transfer, B<train_id> and B<train_no> are set to the "new"
1096             value, the old ones are available in B<old_train_id> and B<old_train_no>.
1097              
1098             =item $result->is_unscheduled
1099              
1100             True if the train does not appear in the requested plans. This can happen
1101             because of two reasons: Either the scheduled time and the actual time are so
1102             far apart that it should've arrived/departed long ago, or it really is an
1103             unscheduled train. In that case, it can be a replacement or an additional
1104             train. There is no logic to distinguish these cases yet.
1105              
1106             =item $result->is_wing
1107              
1108             Returns true if this result is a wing, false otherwise.
1109             A wing is a train which has its own ID and destination, but is currently
1110             coupled to another train and shares all or some of its route.
1111              
1112             =item $result->line
1113              
1114             Train type with line (such as C<< S 1 >>) if available, type with number
1115             (suc as C<< RE 10126 >>) otherwise.
1116              
1117             =item $result->line_no
1118              
1119             Number of the line, undef if unknown. Seems to be set only for S-Bahn and
1120             regional trains. Note that some regional and most long-distance trains do
1121             not have this field set, even if they have a common line number.
1122              
1123             Example: For the line C<< S 1 >>, line_no will return C<< 1 >>.
1124              
1125             =item $result->messages
1126              
1127             Get all qos and delay messages ever entered for this train. Returns a list of
1128             [datetime, string] listrefs sorted by newest first. The datetime part is a
1129             DateTime(3pm) object corresponding to the point in time when the message was
1130             entered, the string is the message. Note that neither duplicates nor superseded
1131             messages are filtered from this list.
1132              
1133             =item $result->old_train_id
1134              
1135             Numeric ID of the pre-transfer train. Seems to be unique for a year and
1136             trackable across stations. Only defined if a transfer took place,
1137             see also B<is_transfer>.
1138              
1139             =item $result->old_train_no
1140              
1141             Number of the pre-tarnsfer train, unique per day. E.g. C<< 2225 >> for
1142             C<< IC 2225 >>. Only defined if a transfer took
1143             place, see also B<is_transfer>.
1144              
1145             =item $result->origin
1146              
1147             Alias for route_start.
1148              
1149             =item $result->qos_messages
1150              
1151             Get all current qos messages for this train. Returns a list of [datetime,
1152             string] listrefs sorted by newest first. The datetime part is a DateTime(3pm)
1153             object corresponding to the point in time when the message was entered, the
1154             string is the message. Contains neither superseded messages nor duplicates (in
1155             case of a duplicate, only the most recent message is present)
1156              
1157             =item $result->platform
1158              
1159             Arrival/departure platform as string, undef if unknown. Note that this is
1160             not neccessarily a number, platform sections may be included (e.g.
1161             C<< 3a/b >>).
1162              
1163             =item $result->raw_id
1164              
1165             Raw ID of the departure, e.g. C<< -4642102742373784975-1401031322-6 >>.
1166             The first part appears to be this train's UUID (can be tracked across
1167             multiple stations), the second the YYmmddHHMM departure timestamp at its
1168             start station, and the third the count of this station in the train's schedule
1169             (in this case, it's the sixth from thestart station).
1170              
1171             About half of all departure IDs do not contain the leading minus (C<< - >>)
1172             seen in this example. The reason for this is unknown.
1173              
1174             This is a developer option. It may be removed without prior warning.
1175              
1176             =item $result->realtime_xml
1177              
1178             XML::LibXML::Node(3pm) object containing all realtime data. undef if none is
1179             available.
1180              
1181             This is a developer option. It may be removed without prior warning.
1182              
1183             =item $result->replaced_by
1184              
1185             Returns a list of references to Travel::Status::DE::IRIS::Result(3pm) objects
1186             which replace the (usually cancelled) arrival/departure of this train.
1187             Returns nothing (false / empty list) otherwise.
1188              
1189             =item $result->replacement_for
1190              
1191             Returns a list of references to Travel::Status::DE::IRIS::Result(3pm) objects
1192             which this (usually unplanned) train is meant to replace.
1193             Returns nothing (false / empty list) otherwise.
1194              
1195             =item $result->route
1196              
1197             List of all stations served by this train, according to its schedule. Does
1198             not contain realtime data.
1199              
1200             =item $result->route_end
1201              
1202             Name of the last station served by this train.
1203              
1204             =item $result->route_interesting
1205              
1206             List of up to three "interesting" stations served by this train, subset of
1207             route_post. Usually contains the next stop and one or two major stations after
1208             that. Does not contain realtime data.
1209              
1210             =item $result->route_pre
1211              
1212             List of station names the train passed (or will have passed) before this stop.
1213              
1214             =item $result->route_post
1215              
1216             List of station names the train will pass after this stop.
1217              
1218             =item $result->route_start
1219              
1220             Name of the first station served by this train.
1221              
1222             =item $result->sched_arrival
1223              
1224             DateTime(3pm) object for the scheduled arrival date and time. undef if the
1225             train starts here.
1226              
1227             =item $result->sched_departure
1228              
1229             DateTime(3pm) object for the scehduled departure date and time. undef if the
1230             train ends here.
1231              
1232             =item $result->sched_platform
1233              
1234             Scheduled Arrival/departure platform as string, undef if unknown. Note that
1235             this is not neccessarily a number, platform sections may be included (e.g. C<<
1236             3a/b >>).
1237              
1238             =item $result->sched_route
1239              
1240             List of all stations served by this train, according to its schedule. Does
1241             not contain realtime data.
1242              
1243             =item $result->sched_route_end
1244              
1245             Name of the last station served by this train according to its schedule.
1246              
1247             =item $result->sched_route_pre
1248              
1249             List of station names the train is scheduled to pass before this stop.
1250              
1251             =item $result->sched_route_post
1252              
1253             List of station names the train is scheduled to pass after this stop.
1254              
1255             =item $result->sched_route_start
1256              
1257             Name of the first station served by this train according to its schedule.
1258              
1259             =item $result->start
1260              
1261             DateTime(3pm) object for the scheduled start of the train on its route
1262             (i.e. the departure time at its first station).
1263              
1264             =item $result->station
1265              
1266             Name of the station this train result belongs to.
1267              
1268             =item $result->station_uic
1269              
1270             EVA number of the station this train result belongs to.
1271             This is often, but not always, identical with the UIC station number.
1272              
1273             =item $result->stop_no
1274              
1275             Number of this stop on the train's route. 1 if it's the start station, 2
1276             for the stop after that, and so on.
1277              
1278             =item $result->time
1279              
1280             Scheduled departure time if available, arrival time otherwise (e.g. if the
1281             train ends here). String in HH:MM format. Does not contain realtime data.
1282              
1283             =item $result->train
1284              
1285             Alias for line.
1286              
1287             =item $result->train_id
1288              
1289             Numeric ID of this train, trackable across stations and days. For instance, the
1290             S 31128 (S1) to Solingen, starting in Dortmund on 19:23, has the ID
1291             2404170432985554630 on each station it passes and (usually) on every day of the
1292             year. Note that it may change during the yearly itinerary update in december.
1293              
1294             =item $result->train_no
1295              
1296             Number of this train, unique per day. E.g. C<< 2225 >> for C<< IC 2225 >>.
1297              
1298             =item $result->type
1299              
1300             Type of this train, e.g. C<< S >> for S-Bahn, C<< RE >> for Regional-Express,
1301             C<< ICE >> for InterCity-Express.
1302              
1303             =item $result->wing_of
1304              
1305             If B<is_wing> is true, returns a weakened reference to the
1306             Travel::Status::DE::IRIS::Result(3pm) object which this train is a wing of. So
1307             far, it seems that a train is either not a wing or a wing of exactly one other
1308             train. Returns undef if B<is_wing> is false.
1309              
1310             =back
1311              
1312             =head2 INTERNAL
1313              
1314             =over
1315              
1316             =item $result = Travel::Status::DE::IRIS::Result->new(I<%data>)
1317              
1318             Returns a new Travel::Status::DE::IRIS::Result object.
1319             You usually do not need to call this.
1320              
1321             =back
1322              
1323             =head1 MESSAGES
1324              
1325             A dump of all messages entered for the result is available. Each message
1326             consists of a timestamp (when it was entered), a type (d for delay reasons,
1327             q for other train-related information) and a value (numeric ID).
1328              
1329             At the time of this writing, the following messages are known:
1330              
1331             =over
1332              
1333             =item d 2 : "Polizeiliche Ermittlung"
1334              
1335             =item d 3 : "Feuerwehreinsatz neben der Strecke"
1336              
1337             =item d 5 : "E<Auml>rztliche Versorgung eines Fahrgastes"
1338              
1339             =item d 6 : "BetE<auml>tigen der Notbremse"
1340              
1341             Source: Correlation between IRIS and DB RIS (bahn.de).
1342              
1343             =item d 7 : "Personen im Gleis"
1344              
1345             =item d 8 : "Notarzteinsatz am Gleis"
1346              
1347             =item d 9 : "Streikauswirkungen"
1348              
1349             =item d 10 : "Ausgebrochene Tiere im Gleis"
1350              
1351             =item d 11 : "Unwetter"
1352              
1353             =item d 13 : "Pass- und Zollkontrolle"
1354              
1355             Source: Correlation between IRIS and DB RIS (bahn.de).
1356              
1357             =item d 15 : "BeeintrE<auml>chtigung durch Vandalismus"
1358              
1359             =item d 16 : "EntschE<auml>rfung einer Fliegerbombe"
1360              
1361             =item d 17 : "BeschE<auml>digung einer BrE<uuml>cke"
1362              
1363             =item d 18 : "UmgestE<uuml>rzter Baum im Gleis"
1364              
1365             =item d 19 : "Unfall an einem BahnE<uuml>bergang"
1366              
1367             =item d 20 : "Tiere im Gleis"
1368              
1369             =item d 21 : "Warten auf weitere Reisende"
1370              
1371             =item d 22 : "Witterungsbedingte StE<ouml>rung"
1372              
1373             =item d 23 : "Feuerwehreinsatz auf BahngelE<auml>nde"
1374              
1375             =item d 24 : "VerspE<auml>tung aus dem Ausland"
1376              
1377             =item d 25 : "Warten auf verspE<auml>tete Zugteile"
1378              
1379             =item d 28 : "GegenstE<auml>nde im Gleis"
1380              
1381             =item d 29 : "Ersatzverkehr mit Bus ist eingerichtet"
1382              
1383             =item d 31 : "Bauarbeiten"
1384              
1385             =item d 32 : "VerzE<ouml>gerung beim Ein-/Ausstieg"
1386              
1387             =item d 33 : "OberleitungsstE<ouml>rung"
1388              
1389             =item d 34 : "SignalstE<ouml>rung"
1390              
1391             =item d 35 : "Streckensperrung"
1392              
1393             =item d 36 : "Technische StE<ouml>rung am Zug"
1394              
1395             =item d 37 : "Technische StE<ouml>rung am Wagen"
1396              
1397             =item d 38 : "Technische StE<ouml>rung an der Strecke"
1398              
1399             =item d 39 : "AnhE<auml>ngen von zusE<auml>tzlichen Wagen"
1400              
1401             =item d 40 : "StellwerksstE<ouml>rung/-ausfall"
1402              
1403             =item d 41 : "StE<ouml>rung an einem BahnE<uuml>bergang"
1404              
1405             =item d 42 : "AuE<szlig>erplanmE<auml>E<szlig>ige GeschwindigkeitsbeschrE<auml>nkung"
1406              
1407             =item d 43 : "VerspE<auml>tung eines vorausfahrenden Zuges"
1408              
1409             =item d 44 : "Warten auf einen entgegenkommenden Zug"
1410              
1411             =item d 45 : "E<Uuml>berholung durch anderen Zug"
1412              
1413             =item d 46 : "Warten auf freie Einfahrt"
1414              
1415             =item d 47 : "VerspE<auml>tete Bereitstellung"
1416              
1417             =item d 48 : "VerspE<auml>tung aus vorheriger Fahrt"
1418              
1419             =item d 55 : "Technische StE<ouml>rung an einem anderen Zug"
1420              
1421             Source: Correlation between IRIS and DB RIS (bahn.de).
1422              
1423             =item d 56 : "Warten auf FahrgE<auml>ste aus einem Bus"
1424              
1425             Source: Correlation between IRIS and DB RIS (bahn.de).
1426              
1427             =item d 57 : "ZusE<auml>tzlicher Halt"
1428              
1429             Source: Correlation between IRIS and DB RIS (bahn.de).
1430              
1431             =item d 58 : "Umleitung"
1432              
1433             Source: Correlation between IRIS and DB RIS (bahn.de). Several entries, related
1434             to "Notarzteinsatz am Gleis".
1435              
1436             =item d 59 : "Schnee und Eis"
1437              
1438             Source: Correlation between IRIS and DB RIS (bahn.de).
1439              
1440             =item d 60 : "Reduzierte Geschwindigkeit wegen Sturm"
1441              
1442             Source: Correlation between IRIS and DB RIS (bahn.de).
1443              
1444             =item d 61 : "TE<uuml>rstE<ouml>rung"
1445              
1446             Source: Correlation between IRIS and DB RIS (bahn.de).
1447              
1448             =item d 62 : "Behobene technische StE<ouml>rung am Zug"
1449              
1450             Source: Correlation between IRIS and DB RIS (bahn.de).
1451              
1452             =item d 63 : "Technische Untersuchung am Zug"
1453              
1454             =item d 64 : "WeichenstE<ouml>rung"
1455              
1456             Source: correlation between IRIS and DB RIS (bahn.de).
1457              
1458             =item d 65 : "Erdrutsch"
1459              
1460             Source: correlation between IRIS and DB RIS (bahn.de).
1461              
1462             =item d 66 : "Hochwasser"
1463              
1464             Source: correlation between IRIS and DB RIS (bahn.de).
1465              
1466             =item f 67 : "BehE<ouml>rdliche Anordnung"
1467              
1468             Source: L<https://twitter.com/DodoMedia/status/1238816272240070659>.
1469              
1470             =item q 70 : "WLAN nicht verfE<uuml>gbar"
1471              
1472             Source: correlation between IRIS and DB RIS (bahn.de).
1473              
1474             =item q 71 : "WLAN in einzelnen Wagen nicht verfE<uuml>gbar"
1475              
1476             =item q 72 : "Info/Entertainment nicht verfE<uuml>gbar"
1477              
1478             =item q 73 : "Mehrzweckabteil vorne"
1479              
1480             Source: correlation between IRIS and DB RIS (bahn.de).
1481              
1482             =item q 74 : "Mehrzweckabteil hinten"
1483              
1484             Source: correlation between IRIS and DB RIS (bahn.de).
1485              
1486             =item q 75 : "1. Klasse vorne"
1487              
1488             Source: correlation between IRIS and DB RIS (bahn.de).
1489              
1490             =item q 76 : "1. Klasse hinten"
1491              
1492             Source: correlation between IRIS and DB RIS (bahn.de).
1493              
1494             =item q 77 : "Ohne 1. Klasse"
1495              
1496             Source: correlation between IRIS and DB RIS (bahn.de).
1497              
1498             =item q 78 : "Ersatzverkehr mit Bus ist eingerichtet"
1499              
1500             =item q 79 : "Ohne Mehrzweckabteil"
1501              
1502             Source: correlation between IRIS and DB RIS (bahn.de).
1503              
1504             =item q 80 : "Abweichende Wagenreihung"
1505              
1506             Verified by L<https://iris.noncd.db.de/irisWebclient/Configuration>.
1507              
1508             =item q 81 : "Fahrzeugtausch"
1509              
1510             =item q 82 : "Mehrere Wagen fehlen"
1511              
1512             Verified by L<https://iris.noncd.db.de/irisWebclient/Configuration>.
1513              
1514             =item q 83 : "StE<ouml>rung der fahrzeuggebundenen Einstiegshilfe"
1515              
1516             =item q 84 : "Zug verkehrt richtig gereiht"
1517              
1518             Obsoletes messages 80, 82, 85.
1519             Verified by L<https://iris.noncd.db.de/irisWebclient/Configuration>.
1520              
1521             =item q 85 : "Ein Wagen fehlt"
1522              
1523             Verified by L<https://iris.noncd.db.de/irisWebclient/Configuration>.
1524              
1525             =item q 86 : "Keine Reservierungsanzeige"
1526              
1527             Verified by L<https://iris.noncd.db.de/irisWebclient/Configuration>.
1528              
1529             =item q 87 : "Einzelne Wagen ohne Reservierungsanzeige"
1530              
1531             Verified by L<https://iris.noncd.db.de/irisWebclient/Configuration>.
1532              
1533             =item q 88 : "Keine QualitE<auml>tsmE<auml>ngel"
1534              
1535             Obsoletes messages 80, 82, 83, 85, 86, 87, 90, 91, 92, 93, 96, 97, 98.
1536             Verified by L<https://iris.noncd.db.de/irisWebclient/Configuration>.
1537              
1538             =item q 89 : "Reservierungen sind wieder vorhanden"
1539              
1540             Obsoletes messages 86, 87.
1541             Verified by L<https://iris.noncd.db.de/irisWebclient/Configuration>.
1542              
1543             =item q 90 : "Kein gastronomisches Angebot"
1544              
1545             Verified by L<https://iris.noncd.db.de/irisWebclient/Configuration>.
1546              
1547             =item q 91 : "EingeschrE<auml>nkte FahrradbefE<ouml>rderung"
1548              
1549             =item q 92 : "Keine FahrradbefE<ouml>rderung"
1550              
1551             =item q 93 : "Fehlende oder gestE<ouml>rte behindertengerechte Einrichtung"
1552              
1553             Verified by L<https://iris.noncd.db.de/irisWebclient/Configuration>.
1554             Might also mean "Kein rollstuhlgerechtes WC" (source: frubi).
1555              
1556             =item q 94 : "Ersatzbewirtschaftung"
1557              
1558             Estimated from a comparison with bahn.de/ris messages. Needs to be verified.
1559              
1560             =item q 95 : "Ohne behindertengerechtes WC"
1561              
1562             Estimated from a comparison with bahn.de/iris messages.
1563              
1564             =item q 96 : "Der Zug ist stark E<uuml>berbesetzt"
1565              
1566             Verified by L<https://iris.noncd.db.de/irisWebclient/Configuration>.
1567              
1568             =item q 97 : "Der Zug ist E<uuml>berbesetzt"
1569              
1570             Verified by L<https://iris.noncd.db.de/irisWebclient/Configuration>.
1571              
1572             =item q 98 : "Sonstige QualitE<auml>tsmE<auml>ngel"
1573              
1574             Verified by L<https://iris.noncd.db.de/irisWebclient/Configuration>.
1575             Might also mean "Kein rollstuhlgerechter Wagen" (source: frubi).
1576              
1577             =item d 99 : "VerzE<ouml>gerungen im Betriebsablauf"
1578              
1579             =back
1580              
1581             =head1 DIAGNOSTICS
1582              
1583             None.
1584              
1585             =head1 DEPENDENCIES
1586              
1587             =over
1588              
1589             =item Class::Accessor(3pm)
1590              
1591             =back
1592              
1593             =head1 BUGS AND LIMITATIONS
1594              
1595             Unknown.
1596              
1597             =head1 SEE ALSO
1598              
1599             Travel::Status::DE::IRIS(3pm).
1600              
1601             =head1 AUTHOR
1602              
1603             Copyright (C) 2013-2023 by Birte Kristina Friesel E<lt>derf@finalrewind.orgE<gt>
1604              
1605             =head1 LICENSE
1606              
1607             This module is licensed under the same terms as Perl itself.