File Coverage

blib/lib/Travel/Status/DE/IRIS/Result.pm
Criterion Covered Total %
statement 311 397 78.3
branch 68 102 66.6
condition 59 119 49.5
subroutine 46 56 82.1
pod 25 43 58.1
total 509 717 70.9


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