File Coverage

blib/lib/Travel/Status/DE/URA.pm
Criterion Covered Total %
statement 165 187 88.2
branch 32 50 64.0
condition 35 61 57.3
subroutine 21 24 87.5
pod 5 7 71.4
total 258 329 78.4


line stmt bran cond sub pod time code
1             package Travel::Status::DE::URA;
2              
3 5     5   86703 use strict;
  5         6  
  5         111  
4 5     5   15 use warnings;
  5         5  
  5         83  
5 5     5   66 use 5.010;
  5         8  
6 5     5   15 use utf8;
  5         5  
  5         18  
7              
8 5     5   2572 no if $] >= 5.018, warnings => 'experimental::smartmatch';
  5         35  
  5         20  
9              
10             our $VERSION = '2.00';
11              
12             # create CONSTANTS for different Return Types
13             use constant {
14 5         1031 TYPE_STOP => 0,
15             TYPE_PREDICTION => 1,
16             TYPE_MESSAGE => 2,
17             TYPE_BASE => 3,
18             TYPE_URA => 4,
19 5     5   376 };
  5         5  
20              
21 5     5   17 use Carp qw(confess cluck);
  5         3  
  5         238  
22 5     5   3530 use DateTime;
  5         423967  
  5         171  
23 5     5   1396 use Encode qw(encode decode);
  5         20244  
  5         279  
24 5     5   2230 use List::MoreUtils qw(firstval none uniq);
  5         32634  
  5         20  
25 5     5   4934 use LWP::UserAgent;
  5         140319  
  5         121  
26 5     5   2257 use Text::CSV;
  5         35557  
  5         22  
27 5     5   2045 use Travel::Status::DE::URA::Result;
  5         10  
  5         22  
28 5     5   1830 use Travel::Status::DE::URA::Stop;
  5         9  
  5         18  
29              
30             sub new {
31 13     13 1 223487 my ( $class, %opt ) = @_;
32              
33 13   50     15 my %lwp_options = %{ $opt{lwp_options} // { timeout => 10 } };
  13         111  
34              
35 13         94 my $ua = LWP::UserAgent->new(%lwp_options);
36 13         10715 my $response;
37              
38 13 100 66     80 if ( not( $opt{ura_base} and $opt{ura_version} ) ) {
39 3         550 confess('ura_base and ura_version are mandatory');
40             }
41              
42             my $self = {
43             datetime => $opt{datetime}
44             // DateTime->now( time_zone => 'Europe/Berlin' ),
45             developer_mode => $opt{developer_mode},
46             ura_base => $opt{ura_base},
47             ura_version => $opt{ura_version},
48             full_routes => $opt{calculate_routes} // 0,
49             hide_past => $opt{hide_past} // 1,
50             stop => $opt{stop},
51             via => $opt{via},
52             via_id => $opt{via_id},
53             stop_id => $opt{stop_id},
54             line_id => $opt{line_id},
55             circle => $opt{circle},
56 10   66     65 post => {
      100        
      100        
57             StopAlso => 'False',
58              
59             # for easier debugging ordered in the returned order
60             ReturnList => 'stoppointname,stopid,stoppointindicator,'
61             . 'latitude,longitude,lineid,linename,'
62             . 'directionid,destinationtext,vehicleid,tripid,estimatedtime'
63             },
64             };
65              
66 10 50       17946 if ( $opt{with_stops} ) {
67 0         0 $self->{post}{StopAlso} = 'True';
68             }
69              
70             $self->{ura_instant_url}
71 10         33 = $self->{ura_base} . '/instant_V' . $self->{ura_version};
72              
73 10         22 bless( $self, $class );
74              
75 10         35 $ua->env_proxy;
76              
77 10 50       13825 if ( substr( $self->{ura_instant_url}, 0, 5 ) ne 'file:' ) {
78              
79             # filter by stop_id only if full_routes is not set
80 0 0 0     0 if ( not $self->{full_routes} and $self->{stop_id} ) {
81 0         0 $self->{post}{StopID} = $self->{stop_id};
82              
83             # filter for via as well to make via work
84 0 0       0 if ( defined $self->{via_id} ) {
85 0         0 $self->{post}{StopID} .= q{,} . $self->{via_id};
86             }
87             }
88              
89             # filter by line
90 0 0       0 if ( $self->{line_id} ) {
91 0         0 $self->{post}{LineID} = $self->{line_id};
92             }
93              
94             # filter for Stops in circle (lon,lat,dist)
95 0 0       0 if ( $self->{circle} ) {
96 0         0 $self->{post}{Circle} = $self->{circle};
97             }
98              
99 0         0 $response = $ua->post( $self->{ura_instant_url}, $self->{post} );
100             }
101             else {
102 10         39 $response = $ua->get( $self->{ura_instant_url} );
103             }
104              
105 10 100       209813 if ( $response->is_error ) {
106 1         9 $self->{errstr} = $response->status_line;
107 1         20 return $self;
108             }
109              
110 9         119 my $raw_str = $response->decoded_content;
111              
112 9 50       543171 if ( $self->{developer_mode} ) {
113 0         0 say decode( 'UTF-8', $raw_str );
114             }
115              
116             # Fix encoding in case we're running through test files
117 9 50       56 if ( substr( $self->{ura_instant_url}, 0, 5 ) eq 'file:' ) {
118 9         39 $raw_str = encode( 'UTF-8', $raw_str );
119             }
120 9         270306 $self->parse_raw_data($raw_str);
121              
122 9         21988 return $self;
123             }
124              
125             sub parse_raw_data {
126 9     9 0 26 my ( $self, $raw_str ) = @_;
127 9         139 my $csv = Text::CSV->new( { binary => 1 } );
128              
129 9         51735 for my $dep ( split( /\r\n/, $raw_str ) ) {
130 155673         413503 $dep =~ s{^\[}{};
131 155673         289460 $dep =~ s{\]$}{};
132              
133 155673         275513 $csv->parse($dep);
134 155673         44540197 my @fields = $csv->fields;
135              
136             # encode all fields
137 155673         950109 for my $i ( 1, 11 ) {
138 311346         3479971 $fields[$i] = encode( 'UTF-8', $fields[$i] );
139             }
140              
141 155673         2393929 push( @{ $self->{raw_list} }, \@fields );
  155673         216056  
142              
143 155673         131232 my $type = $fields[0];
144              
145 155673 100       304972 if ( $type == TYPE_STOP ) {
146 9891         7278 my $stop_name = $fields[1];
147 9891         6239 my $stop_id = $fields[2];
148 9891         6921 my $longitude = $fields[3];
149 9891         6188 my $latitude = $fields[4];
150              
151             # create Stop Dict
152 9891 50       17187 if ( not exists $self->{stops}{$stop_id} ) {
153 9891         14534 $self->{stops}{$stop_id} = Travel::Status::DE::URA::Stop->new(
154             name => decode( 'UTF-8', $stop_name ),
155             id => $stop_id,
156             longitude => $longitude,
157             latitude => $latitude,
158             );
159             }
160             }
161 155673 100       208157 if ( $type == TYPE_PREDICTION ) {
162 145773         102176 push( @{ $self->{stop_names} }, $fields[1] );
  145773         235912  
163             }
164             }
165              
166 9         27757 @{ $self->{stop_names} } = uniq @{ $self->{stop_names} };
  9         16178  
  9         53568  
167              
168 9         1745 return $self;
169             }
170              
171             sub get_stop_by_name {
172 3     3 1 1153 my ( $self, $name ) = @_;
173              
174 3         7 my $nname = lc($name);
175 3     1098   12 my $actual_match = firstval { $nname eq lc($_) } @{ $self->{stop_names} };
  1098         678  
  3         43  
176              
177 3 100       13 if ($actual_match) {
178 2         7 return $actual_match;
179             }
180              
181 1         1 return ( grep { $_ =~ m{$name}i } @{ $self->{stop_names} } );
  901         952  
  1         5  
182             }
183              
184             sub get_stops {
185 0     0 1 0 my ($self) = @_;
186              
187 0         0 return $self->{stops};
188             }
189              
190             sub errstr {
191 2     2 1 1646 my ($self) = @_;
192              
193 2         12 return $self->{errstr};
194             }
195              
196             sub results {
197 12     12 1 68141 my ( $self, %opt ) = @_;
198 12         25 my @results;
199              
200 12   66     117 my $full_routes = $opt{calculate_routes} // $self->{full_routes} // 0;
      50        
201 12   66     73 my $hide_past = $opt{hide_past} // $self->{hide_past} // 1;
      50        
202 12   33     152 my $line_id = $opt{line_id} // $self->{line_id};
203 12   66     161 my $stop = $opt{stop} // $self->{stop};
204 12   33     51 my $stop_id = $opt{stop_id} // $self->{stop_id};
205 12   66     39 my $via = $opt{via} // $self->{via};
206 12   33     39 my $via_id = $opt{via_id} // $self->{via_id};
207              
208 12         20 my $dt_now = $self->{datetime};
209 12         83 my $ts_now = $dt_now->epoch;
210              
211 12 100 66     257 if ( $via or $via_id ) {
212 2         4 $full_routes = 1;
213             }
214              
215 12         20 for my $dep ( @{ $self->{raw_list} } ) {
  12         52  
216              
217             my (
218             $type, $stopname, $stopid, $stopindicator,
219             $longitude, $latitude, $lineid, $linename,
220             $directionid, $dest, $vehicleid, $tripid,
221             $timestamp
222 190267         115018 ) = @{$dep};
  190267         597267  
223 190267         169392 my ( @route_pre, @route_post );
224              
225             # only work on Prediction informations
226 190267 100       239622 if ( $type != TYPE_PREDICTION ) {
227 12100         10570 next;
228             }
229              
230 178167 50 33     234035 if ( $line_id and not( $lineid eq $line_id ) ) {
231 0         0 next;
232             }
233              
234 178167 100 100     357800 if ( $stop and not( $stopname eq $stop ) ) {
235 111872         107927 next;
236             }
237              
238 66295 50 33     94356 if ( $stop_id and not( $stopid eq $stop_id ) ) {
239 0         0 next;
240             }
241              
242 66295 50       80606 if ( not $timestamp ) {
243 0         0 cluck("departure element without timestamp: $dep");
244 0         0 next;
245             }
246              
247 66295         55315 $timestamp /= 1000;
248              
249 66295 100 66     135916 if ( $hide_past and $ts_now > $timestamp ) {
250 32394         30180 next;
251             }
252              
253 33901         78791 my $dt_dep = DateTime->from_epoch(
254             epoch => $timestamp,
255             time_zone => 'Europe/Berlin'
256             );
257 33901         10771736 my $ts_dep = $dt_dep->epoch;
258              
259 33901 100       179809 if ($full_routes) {
260             my @route
261 22577         77498 = map { [ $_->[12] / 1000, $_->[1], $_->[2], $_->[4], $_->[5] ] }
262 12261129         11662508 grep { $_->[11] == $tripid }
263 757         1364 grep { $_->[0] == 1 } @{ $self->{raw_list} };
  13093829         12422907  
  757         24638  
264              
265 757         2306 @route_pre = grep { $_->[0] < $ts_dep } @route;
  22577         23712  
266 757         1704 @route_post = grep { $_->[0] > $ts_dep } @route;
  22577         18769  
267              
268 757 100 100     14010 if ( $via
269 10790     10790   9520 and none { $_->[1] eq $via } @route_post )
270             {
271 740         18423 next;
272             }
273              
274 17 50 33     103 if ( $via_id
275 0     0   0 and none { $_->[2] eq $via_id } @route_post )
276             {
277 0         0 next;
278             }
279              
280 17 50       64 if ($hide_past) {
281 0         0 @route_pre = grep { $_->[0] >= $ts_now } @route_pre;
  0         0  
282             }
283              
284 207         205 @route_pre = map { $_->[0] }
285 553         413 sort { $a->[1] <=> $b->[1] }
286 17         47 map { [ $_, $_->[0] ] } @route_pre;
  207         408  
287 497         433 @route_post = map { $_->[0] }
288 1960         1270 sort { $a->[1] <=> $b->[1] }
289 17         67 map { [ $_, $_->[0] ] } @route_post;
  497         616  
290              
291             @route_pre = map {
292 17         91 Travel::Status::DE::URA::Stop->new(
  207         717  
293             datetime => DateTime->from_epoch(
294             epoch => $_->[0],
295             time_zone => 'Europe/Berlin'
296             ),
297             name => decode( 'UTF-8', $_->[1] ),
298             id => $_->[2],
299             longitude => $_->[3],
300             latitude => $_->[4],
301             )
302             } @route_pre;
303             @route_post = map {
304 17         52 Travel::Status::DE::URA::Stop->new(
  497         1294  
305             datetime => DateTime->from_epoch(
306             epoch => $_->[0],
307             time_zone => 'Europe/Berlin'
308             ),
309             name => decode( 'UTF-8', $_->[1] ),
310             id => $_->[2],
311             longitude => $_->[3],
312             latitude => $_->[4],
313             )
314             } @route_post;
315             }
316              
317             push(
318 33161         111303 @results,
319             Travel::Status::DE::URA::Result->new(
320             datetime => $dt_dep,
321             dt_now => $dt_now,
322             line => $linename,
323             line_id => $lineid,
324             destination => $dest,
325             route_pre => [@route_pre],
326             route_post => [@route_post],
327             stop => $stopname,
328             stop_id => $stopid,
329             stop_indicator => $stopindicator,
330             )
331             );
332             }
333              
334 33161         40700 @results = map { $_->[0] }
335 397196         253317 sort { $a->[1] <=> $b->[1] }
336 12         171 map { [ $_, $_->datetime->epoch ] } @results;
  33161         233842  
337              
338 12         99682 return @results;
339             }
340              
341             # static
342             sub get_services {
343             return (
344             {
345 0     0 0   ura_base => 'http://ivu.aseag.de/interfaces/ura',
346             ura_version => 1,
347             name => 'Aachener Straßenbahn und Energieversorgungs AG',
348             shortname => 'ASEAG',
349             },
350             {
351             ura_base => 'http://countdown.api.tfl.gov.uk/interfaces/ura',
352             ura_version => 1,
353             name => 'Transport for London',
354             shortname => 'TfL',
355             }
356             );
357             }
358              
359             1;
360              
361             __END__