File Coverage

blib/lib/Travel/Status/DE/URA.pm
Criterion Covered Total %
statement 162 183 88.5
branch 32 50 64.0
condition 35 61 57.3
subroutine 20 22 90.9
pod 5 6 83.3
total 254 322 78.8


line stmt bran cond sub pod time code
1             package Travel::Status::DE::URA;
2              
3 5     5   92539 use strict;
  5         7  
  5         122  
4 5     5   17 use warnings;
  5         627  
  5         102  
5 5     5   83 use 5.010;
  5         10  
6              
7 5     5   2500 no if $] >= 5.018, warnings => 'experimental::smartmatch';
  5         35  
  5         21  
8              
9             our $VERSION = '1.02';
10              
11             # create CONSTANTS for different Return Types
12             use constant {
13 5         449 TYPE_STOP => 0,
14             TYPE_PREDICTION => 1,
15             TYPE_MESSAGE => 2,
16             TYPE_BASE => 3,
17             TYPE_URA => 4,
18 5     5   373 };
  5         6  
19              
20 5     5   17 use Carp qw(confess cluck);
  5         4  
  5         273  
21 5     5   4200 use DateTime;
  5         357966  
  5         168  
22 5     5   1433 use Encode qw(encode decode);
  5         21124  
  5         277  
23 5     5   2272 use List::MoreUtils qw(firstval none uniq);
  5         37320  
  5         32  
24 5     5   5230 use LWP::UserAgent;
  5         145664  
  5         138  
25 5     5   2294 use Text::CSV;
  5         35957  
  5         25  
26 5     5   2280 use Travel::Status::DE::URA::Result;
  5         10  
  5         31  
27 5     5   2080 use Travel::Status::DE::URA::Stop;
  5         8  
  5         19  
28              
29             sub new {
30 13     13 1 220452 my ( $class, %opt ) = @_;
31              
32 13   50     20 my %lwp_options = %{ $opt{lwp_options} // { timeout => 10 } };
  13         111  
33              
34 13         97 my $ua = LWP::UserAgent->new(%lwp_options);
35 13         10865 my $response;
36              
37 13 100 66     74 if ( not( $opt{ura_base} and $opt{ura_version} ) ) {
38 3         546 confess('ura_base and ura_version are mandatory');
39             }
40              
41             my $self = {
42             datetime => $opt{datetime}
43             // DateTime->now( time_zone => 'Europe/Berlin' ),
44             developer_mode => $opt{developer_mode},
45             ura_base => $opt{ura_base},
46             ura_version => $opt{ura_version},
47             full_routes => $opt{calculate_routes} // 0,
48             hide_past => $opt{hide_past} // 1,
49             stop => $opt{stop},
50             via => $opt{via},
51             via_id => $opt{via_id},
52             stop_id => $opt{stop_id},
53             line_id => $opt{line_id},
54             circle => $opt{circle},
55 10   66     59 post => {
      100        
      100        
56             StopAlso => 'False',
57              
58             # for easier debugging ordered in the returned order
59             ReturnList => 'stoppointname,stopid,stoppointindicator,'
60             . 'latitude,longitude,lineid,linename,'
61             . 'directionid,destinationtext,vehicleid,tripid,estimatedtime'
62             },
63             };
64              
65 10 50       18142 if ( $opt{with_stops} ) {
66 0         0 $self->{post}{StopAlso} = 'True';
67             }
68              
69             $self->{ura_instant_url}
70 10         33 = $self->{ura_base} . '/instant_V' . $self->{ura_version};
71              
72 10         20 bless( $self, $class );
73              
74 10         39 $ua->env_proxy;
75              
76 10 50       14538 if ( substr( $self->{ura_instant_url}, 0, 5 ) ne 'file:' ) {
77              
78             # filter by stop_id only if full_routes is not set
79 0 0 0     0 if ( not $self->{full_routes} and $self->{stop_id} ) {
80 0         0 $self->{post}{StopID} = $self->{stop_id};
81              
82             # filter for via as well to make via work
83 0 0       0 if ( defined $self->{via_id} ) {
84 0         0 $self->{post}{StopID} .= q{,} . $self->{via_id};
85             }
86             }
87              
88             # filter by line
89 0 0       0 if ( $self->{line_id} ) {
90 0         0 $self->{post}{LineID} = $self->{line_id};
91             }
92              
93             # filter for Stops in circle (lon,lat,dist)
94 0 0       0 if ( $self->{circle} ) {
95 0         0 $self->{post}{Circle} = $self->{circle};
96             }
97              
98 0         0 $response = $ua->post( $self->{ura_instant_url}, $self->{post} );
99             }
100             else {
101 10         39 $response = $ua->get( $self->{ura_instant_url} );
102             }
103              
104 10 100       212786 if ( $response->is_error ) {
105 1         10 $self->{errstr} = $response->status_line;
106 1         20 return $self;
107             }
108              
109 9         116 my $raw_str = $response->decoded_content;
110              
111 9 50       549980 if ( $self->{developer_mode} ) {
112 0         0 say decode( 'UTF-8', $raw_str );
113             }
114              
115             # Fix encoding in case we're running through test files
116 9 50       59 if ( substr( $self->{ura_instant_url}, 0, 5 ) eq 'file:' ) {
117 9         46 $raw_str = encode( 'UTF-8', $raw_str );
118             }
119 9         267791 $self->parse_raw_data($raw_str);
120              
121 9         21245 return $self;
122             }
123              
124             sub parse_raw_data {
125 9     9 0 30 my ( $self, $raw_str ) = @_;
126 9         158 my $csv = Text::CSV->new( { binary => 1 } );
127              
128 9         51625 for my $dep ( split( /\r\n/, $raw_str ) ) {
129 155673         395836 $dep =~ s{^\[}{};
130 155673         290314 $dep =~ s{\]$}{};
131              
132 155673         268875 $csv->parse($dep);
133 155673         44822415 my @fields = $csv->fields;
134              
135             # encode all fields
136 155673         954209 for my $i ( 1, 11 ) {
137 311346         3508489 $fields[$i] = encode( 'UTF-8', $fields[$i] );
138             }
139              
140 155673         2402823 push( @{ $self->{raw_list} }, \@fields );
  155673         213484  
141              
142 155673         133412 my $type = $fields[0];
143              
144 155673 100       295388 if ( $type == TYPE_STOP ) {
145 9891         7420 my $stop_name = $fields[1];
146 9891         6162 my $stop_id = $fields[2];
147 9891         6463 my $longitude = $fields[3];
148 9891         6268 my $latitude = $fields[4];
149              
150             # create Stop Dict
151 9891 50       16263 if ( not exists $self->{stops}{$stop_id} ) {
152 9891         13974 $self->{stops}{$stop_id} = Travel::Status::DE::URA::Stop->new(
153             name => decode( 'UTF-8', $stop_name ),
154             id => $stop_id,
155             longitude => $longitude,
156             latitude => $latitude,
157             );
158             }
159             }
160 155673 100       208543 if ( $type == TYPE_PREDICTION ) {
161 145773         97708 push( @{ $self->{stop_names} }, $fields[1] );
  145773         241080  
162             }
163             }
164              
165 9         24462 @{ $self->{stop_names} } = uniq @{ $self->{stop_names} };
  9         13880  
  9         49840  
166              
167 9         1544 return $self;
168             }
169              
170             sub get_stop_by_name {
171 3     3 1 858 my ( $self, $name ) = @_;
172              
173 3         5 my $nname = lc($name);
174 3     1098   12 my $actual_match = firstval { $nname eq lc($_) } @{ $self->{stop_names} };
  1098         697  
  3         36  
175              
176 3 100       15 if ($actual_match) {
177 2         6 return $actual_match;
178             }
179              
180 1         1 return ( grep { $_ =~ m{$name}i } @{ $self->{stop_names} } );
  901         906  
  1         5  
181             }
182              
183             sub get_stops {
184 0     0 1 0 my ($self) = @_;
185              
186 0         0 return $self->{stops};
187             }
188              
189             sub errstr {
190 2     2 1 1468 my ($self) = @_;
191              
192 2         11 return $self->{errstr};
193             }
194              
195             sub results {
196 12     12 1 62407 my ( $self, %opt ) = @_;
197 12         21 my @results;
198              
199 12   66     103 my $full_routes = $opt{calculate_routes} // $self->{full_routes} // 0;
      50        
200 12   66     73 my $hide_past = $opt{hide_past} // $self->{hide_past} // 1;
      50        
201 12   33     48 my $line_id = $opt{line_id} // $self->{line_id};
202 12   66     51 my $stop = $opt{stop} // $self->{stop};
203 12   33     45 my $stop_id = $opt{stop_id} // $self->{stop_id};
204 12   66     39 my $via = $opt{via} // $self->{via};
205 12   33     36 my $via_id = $opt{via_id} // $self->{via_id};
206              
207 12         22 my $dt_now = $self->{datetime};
208 12         69 my $ts_now = $dt_now->epoch;
209              
210 12 100 66     173 if ( $via or $via_id ) {
211 2         4 $full_routes = 1;
212             }
213              
214 12         18 for my $dep ( @{ $self->{raw_list} } ) {
  12         49  
215              
216             my (
217             $type, $stopname, $stopid, $stopindicator,
218             $longitude, $latitude, $lineid, $linename,
219             $directionid, $dest, $vehicleid, $tripid,
220             $timestamp
221 190267         115834 ) = @{$dep};
  190267         580302  
222 190267         163876 my ( @route_pre, @route_post );
223              
224             # only work on Prediction informations
225 190267 100       243651 if ( $type != TYPE_PREDICTION ) {
226 12100         10057 next;
227             }
228              
229 178167 50 33     233772 if ( $line_id and not( $lineid eq $line_id ) ) {
230 0         0 next;
231             }
232              
233 178167 100 100     354913 if ( $stop and not( $stopname eq $stop ) ) {
234 111872         102285 next;
235             }
236              
237 66295 50 33     91623 if ( $stop_id and not( $stopid eq $stop_id ) ) {
238 0         0 next;
239             }
240              
241 66295 50       78493 if ( not $timestamp ) {
242 0         0 cluck("departure element without timestamp: $dep");
243 0         0 next;
244             }
245              
246 66295         54990 $timestamp /= 1000;
247              
248 66295 100 66     133126 if ( $hide_past and $ts_now > $timestamp ) {
249 32394         29592 next;
250             }
251              
252 33901         74887 my $dt_dep = DateTime->from_epoch(
253             epoch => $timestamp,
254             time_zone => 'Europe/Berlin'
255             );
256 33901         11015034 my $ts_dep = $dt_dep->epoch;
257              
258 33901 100       182273 if ($full_routes) {
259             my @route
260 22577         69736 = map { [ $_->[12] / 1000, $_->[1], $_->[2], $_->[4], $_->[5] ] }
261 12261129         10719375 grep { $_->[11] == $tripid }
262 757         1082 grep { $_->[0] == 1 } @{ $self->{raw_list} };
  13093829         11589876  
  757         23676  
263              
264 757         2549 @route_pre = grep { $_->[0] < $ts_dep } @route;
  22577         24115  
265 757         1523 @route_post = grep { $_->[0] > $ts_dep } @route;
  22577         17840  
266              
267 757 100 100     12739 if ( $via
268 10790     10790   9274 and none { $_->[1] eq $via } @route_post )
269             {
270 740         17213 next;
271             }
272              
273 17 50 33     96 if ( $via_id
274 0     0   0 and none { $_->[2] eq $via_id } @route_post )
275             {
276 0         0 next;
277             }
278              
279 17 50       49 if ($hide_past) {
280 0         0 @route_pre = grep { $_->[0] >= $ts_now } @route_pre;
  0         0  
281             }
282              
283 207         188 @route_pre = map { $_->[0] }
284 553         389 sort { $a->[1] <=> $b->[1] }
285 17         38 map { [ $_, $_->[0] ] } @route_pre;
  207         465  
286 497         405 @route_post = map { $_->[0] }
287 1960         1244 sort { $a->[1] <=> $b->[1] }
288 17         59 map { [ $_, $_->[0] ] } @route_post;
  497         533  
289              
290             @route_pre = map {
291 17         85 Travel::Status::DE::URA::Stop->new(
  207         884  
292             datetime => DateTime->from_epoch(
293             epoch => $_->[0],
294             time_zone => 'Europe/Berlin'
295             ),
296             name => decode( 'UTF-8', $_->[1] ),
297             id => $_->[2],
298             longitude => $_->[3],
299             latitude => $_->[4],
300             )
301             } @route_pre;
302             @route_post = map {
303 17         46 Travel::Status::DE::URA::Stop->new(
  497         1283  
304             datetime => DateTime->from_epoch(
305             epoch => $_->[0],
306             time_zone => 'Europe/Berlin'
307             ),
308             name => decode( 'UTF-8', $_->[1] ),
309             id => $_->[2],
310             longitude => $_->[3],
311             latitude => $_->[4],
312             )
313             } @route_post;
314             }
315              
316             push(
317 33161         104959 @results,
318             Travel::Status::DE::URA::Result->new(
319             datetime => $dt_dep,
320             dt_now => $dt_now,
321             line => $linename,
322             line_id => $lineid,
323             destination => $dest,
324             route_pre => [@route_pre],
325             route_post => [@route_post],
326             stop => $stopname,
327             stop_id => $stopid,
328             stop_indicator => $stopindicator,
329             )
330             );
331             }
332              
333 33161         43509 @results = map { $_->[0] }
334 397196         251629 sort { $a->[1] <=> $b->[1] }
335 12         116 map { [ $_, $_->datetime->epoch ] } @results;
  33161         236565  
336              
337 12         92490 return @results;
338             }
339              
340             1;
341              
342             __END__